CDMA UI - 3-way calling, call waiting, and display info records
Wink Saville [Thu, 25 Jun 2009 05:15:07 +0000 (22:15 -0700)]
This is dependent on 5338 to add the string resource
network_message.

res/values/ids.xml [changed mode: 0644->0755]
res/values/strings.xml
src/com/android/phone/CallCard.java [changed mode: 0644->0755]
src/com/android/phone/CallNotifier.java [changed mode: 0644->0755]
src/com/android/phone/CdmaDisplayInfo.java [new file with mode: 0755]
src/com/android/phone/CdmaPhoneCallState.java [new file with mode: 0755]
src/com/android/phone/InCallMenu.java [changed mode: 0644->0755]
src/com/android/phone/InCallScreen.java [changed mode: 0644->0755]
src/com/android/phone/PhoneApp.java [changed mode: 0644->0755]
src/com/android/phone/PhoneUtils.java [changed mode: 0644->0755]

old mode 100644 (file)
new mode 100755 (executable)
index 1286a9a..03f59a5
@@ -28,4 +28,6 @@
     <item type="id" name="menuHold" />
     <item type="id" name="menuAnswerAndHold" />
     <item type="id" name="menuAnswerAndEnd" />
+    <item type="id" name="menuAnswer" />
+    <item type="id" name="menuIgnore" />
 </resources>
index 8f49da9..2edfb84 100755 (executable)
     <string name="menuButtonHint">Press Menu for call options.</string>
     <!-- In-call screen, message just under call window when keyboard is revealed -->
     <string name="menuButtonKeyboardDialHint">"Press Menu for call options  \u2022  Use keyboard to dial"</string>
+    <!-- Incoming call menu: Label for "Answer" menu item -->
+    <string name="menu_answer">Answer</string>
+    <!-- Incoming call menu: Label for "Ignore" menu item -->
+    <string name="menu_ignore">Ignore</string>
 
     <!-- post dial -->
     <!-- In-call screen: body text of the dialog that appears when we encounter
     <string name="card_title_on_hold">On hold</string>
     <!-- In-call screen: status label for a call that's in the process of hanging up -->
     <string name="card_title_hanging_up">Hanging up</string>
+    <!-- In-call screen: status label for a call that's in CDMA flash mode -->
+    <string name="card_title_in_call">In call</string>
 
     <!-- Notification strings -->
     <!-- Missed call notification label, used when there's exactly one missed call -->
old mode 100644 (file)
new mode 100755 (executable)
index 3874845..1fc197b
@@ -40,6 +40,8 @@ import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.util.List;
+
 /**
  * "Call card" UI element: the in-call screen contains a tiled layout of call
  * cards, each representing the state of a current "call" (ie. an active call,
@@ -58,6 +60,9 @@ public class CallCard extends FrameLayout
      */
     private InCallScreen mInCallScreen;
 
+    // Phone app instance
+    private PhoneApp mApplication;
+
     // Top-level subviews of the CallCard
     private ViewGroup mMainCallCard;
     private ViewGroup mOtherCallOngoingInfoArea;
@@ -120,6 +125,8 @@ public class CallCard extends FrameLayout
                 this,                // root
                 true);
 
+        mApplication = PhoneApp.getInstance();
+
         mCallTime = new CallTime(this);
 
         // create a new object to track the state for the photo.
@@ -254,14 +261,6 @@ public class CallCard extends FrameLayout
         Call fgCall = phone.getForegroundCall();
         Call bgCall = phone.getBackgroundCall();
 
-        // Check for the "generic call" state.
-        if (fgCall.isGeneric()) {
-            // Show the special "generic call" state instead of the regular
-            // in-call CallCard state.
-            updateGenericCall(phone);
-            return;
-        }
-
         if (fgCall.isIdle() && !fgCall.hasConnections()) {
             if (DBG) log("updateForegroundCall: no active call, show holding call");
             // TODO: make sure this case agrees with the latest UI spec.
@@ -277,7 +276,20 @@ public class CallCard extends FrameLayout
         }
 
         displayMainCallStatus(phone, fgCall);
-        displayOnHoldCallStatus(phone, bgCall);
+
+        if (phone.getPhoneName().equals("CDMA")) {
+            if (mApplication.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                displayOnHoldCallStatus(phone, fgCall);
+            } else {
+                //This is required so that even if a background call is not present
+                // we need to clean up the background call area.
+                displayOnHoldCallStatus(phone, bgCall);
+            }
+        } else {
+            displayOnHoldCallStatus(phone, bgCall);
+        }
+
         displayOngoingCallStatus(phone, null);
     }
 
@@ -317,9 +329,7 @@ public class CallCard extends FrameLayout
 
     /**
      * Updates the UI for the state where the phone is not in use.
-
      * This is analogous to updateForegroundCall() and updateRingingCall(),
-
      * but for the (uncommon) case where the phone is
      * totally idle.  (See comments in updateState() above.)
      *
@@ -361,7 +371,7 @@ public class CallCard extends FrameLayout
         boolean landscapeMode = InCallScreen.ConfigurationHelper.isLandscape();
 
         // Background images are also different if Bluetooth is active.
-        final boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+        final boolean bluetoothActive = mApplication.showBluetoothIndication();
 
         switch (state) {
             case ACTIVE:
@@ -480,7 +490,12 @@ public class CallCard extends FrameLayout
         } else {
             // Update onscreen info for a regular call (which presumably
             // has only one connection.)
-            Connection conn = call.getEarliestConnection();
+            Connection conn = null;
+            if (phone.getPhoneName().equals("CDMA")) {
+                conn = call.getLatestConnection();
+            } else { // GSM.
+                conn = call.getEarliestConnection();
+            }
 
             if (conn == null) {
                 if (DBG) log("displayMainCallStatus: connection is null, using default values.");
@@ -560,7 +575,7 @@ public class CallCard extends FrameLayout
         boolean landscapeMode = InCallScreen.ConfigurationHelper.isLandscape();
 
         // Background images are also different if Bluetooth is active.
-        final boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+        final boolean bluetoothActive = mApplication.showBluetoothIndication();
 
         showCallConnected();
 
@@ -643,21 +658,38 @@ public class CallCard extends FrameLayout
 
         // We display *either* the "upper title" or the "lower title", but
         // never both.
-        if (state == Call.State.ACTIVE) {
-            // Use the "lower title" (in green).
-            mLowerTitleViewGroup.setVisibility(View.VISIBLE);
 
-            final boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+        if (state == Call.State.ACTIVE) {
+            final boolean bluetoothActive = mApplication.showBluetoothIndication();
             int ongoingCallIcon = bluetoothActive ? R.drawable.ic_incall_ongoing_bluetooth
                     : R.drawable.ic_incall_ongoing;
-            mLowerTitleIcon.setImageResource(ongoingCallIcon);
-
-            mLowerTitle.setText(cardTitle);
-
             int textColor = bluetoothActive ? mTextColorConnectedBluetooth : mTextColorConnected;
-            mLowerTitle.setTextColor(textColor);
-            mElapsedTime.setTextColor(textColor);
-            setUpperTitle("");
+
+            if (mApplication.phone.getPhoneName().equals("CDMA")) {
+               // Check if the "Dialing" 3Way call needs to be displayed
+               // as the Foreground Call state still remains ACTIVE
+               if (mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+                    // Use the "upper title":
+                    mUpperTitle.setText(cardTitle);
+                    mLowerTitleViewGroup.setVisibility(View.INVISIBLE);
+               } else {
+                    // Use the "lower title" (in green).
+                    mLowerTitleViewGroup.setVisibility(View.VISIBLE);
+                    mLowerTitle.setText(cardTitle);
+                    mLowerTitleIcon.setImageResource(ongoingCallIcon);
+                    mLowerTitle.setTextColor(textColor);
+                    mElapsedTime.setTextColor(textColor);
+                    mUpperTitle.setText("");
+               }
+            } else { // GSM
+                // Use the "lower title" (in green).
+                mLowerTitleViewGroup.setVisibility(View.VISIBLE);
+                mLowerTitleIcon.setImageResource(ongoingCallIcon);
+                mLowerTitle.setText(cardTitle);
+                mLowerTitle.setTextColor(textColor);
+                mElapsedTime.setTextColor(textColor);
+                setUpperTitle("");
+            }
         } else if (state == Call.State.DISCONNECTED) {
             // Use the "lower title" (in red).
             // TODO: We may not *always* want to use the lower title for
@@ -726,7 +758,15 @@ public class CallCard extends FrameLayout
             case ACTIVE:
                 // Title is "Call in progress".  (Note this appears in the
                 // "lower title" area of the CallCard.)
-                retVal = context.getString(R.string.card_title_in_progress);
+                if (mApplication.phone.getPhoneName().equals("CDMA")) {
+                    if (mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+                        retVal = context.getString(R.string.card_title_dialing);
+                    } else {
+                        retVal = context.getString(R.string.card_title_in_progress);
+                    }
+                } else { //GSM
+                    retVal = context.getString(R.string.card_title_in_progress);
+                }
                 break;
 
             case HOLDING:
@@ -768,12 +808,12 @@ public class CallCard extends FrameLayout
             return;
         }
 
+        String name = null;
         Call.State state = call.getState();
         switch (state) {
             case HOLDING:
                 // Ok, there actually is a background call on hold.
                 // Display the "on hold" box.
-                String name;
 
                 // First, see if we need to query.
                 if (PhoneUtils.isConferenceCall(call)) {
@@ -801,6 +841,40 @@ public class CallCard extends FrameLayout
 
                 break;
 
+            case ACTIVE:
+                // CDMA: This is because in CDMA when the user originates the second call,
+                // although the Foreground call state is still ACTIVE in reality the network
+                // put the first call on hold.
+                if (mApplication.phone.getPhoneName().equals("CDMA")) {
+                    List<Connection> connections = call.getConnections();
+                    if (connections.size() > 2) {
+                        // This means that current Mobile Originated call is the not the first 3-Way
+                        // call the user is making, which in turn tells the PhoneApp that we no
+                        // longer know which previous caller/party had dropped out before the user
+                        // made this call.
+                        name = getContext().getString(R.string.card_title_in_call);
+                    } else {
+                        // This means that the current Mobile Originated call IS the first 3-Way
+                        // and hence we display the first callers/party's info here.
+                        Connection conn = call.getEarliestConnection();
+                        PhoneUtils.CallerInfoToken info = PhoneUtils.startGetCallerInfo(
+                                getContext(), conn, this, mOtherCallOnHoldName);
+
+                        name = PhoneUtils.getCompactNameFromCallerInfo(info.currentInfo,
+                                getContext());
+                    }
+
+                    mOtherCallOnHoldName.setText(name);
+
+                    // The call here is either in Callwaiting or 3way, use the orange "hold" frame
+                    // and orange text color:
+                    setOnHoldInfoAreaBackgroundResource(R.drawable.incall_frame_hold_short);
+                    mOtherCallOnHoldName.setTextColor(mTextColorOnHold);
+                    mOtherCallOnHoldStatus.setTextColor(mTextColorOnHold);
+                    mOtherCallOnHoldInfoArea.setVisibility(View.VISIBLE);
+                }
+                break;
+
             default:
                 // There's actually no call on hold.  (Presumably this call's
                 // state is IDLE, since any other state is meaningless for the
@@ -834,7 +908,9 @@ public class CallCard extends FrameLayout
                 String name;
 
                 // First, see if we need to query.
-                if (PhoneUtils.isConferenceCall(call)) {
+                if (call.isGeneric()) {
+                    name = getContext().getString(R.string.card_title_in_call);
+                } else if (PhoneUtils.isConferenceCall(call)) {
                     name = getContext().getString(R.string.confCall);
                 } else {
                     // perform query and update the name temporarily
@@ -850,7 +926,7 @@ public class CallCard extends FrameLayout
                 // This is an "ongoing" call: we normally use the green
                 // background frame and text color, but we use blue
                 // instead if bluetooth is in use.
-                boolean bluetoothActive = PhoneApp.getInstance().showBluetoothIndication();
+                boolean bluetoothActive = mApplication.showBluetoothIndication();
 
                 int ongoingCallBackground =
                         bluetoothActive ? R.drawable.incall_frame_bluetooth_short
@@ -1012,7 +1088,12 @@ public class CallCard extends FrameLayout
         } else {
             name =  getPresentationString(presentation);
         }
-        mName.setText(name);
+
+        if (call.isGeneric()) {
+            mName.setText(R.string.card_title_in_call);
+        } else {
+            mName.setText(name);
+        }
         mName.setVisibility(View.VISIBLE);
 
         // Update mPhoto
@@ -1035,7 +1116,7 @@ public class CallCard extends FrameLayout
             ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(info, 0, this, call,
                     getContext(), mPhoto, personUri, -1);
         }
-        if (displayNumber != null) {
+        if (displayNumber != null && !call.isGeneric()) {
             mPhoneNumber.setText(displayNumber);
             mPhoneNumber.setVisibility(View.VISIBLE);
         } else {
@@ -1071,12 +1152,20 @@ public class CallCard extends FrameLayout
     private void updateDisplayForConference() {
         if (DBG) log("updateDisplayForConference()...");
 
-        // Display the "conference call" image in the photo slot,
-        // with no other information.
-
-        showImage(mPhoto, R.drawable.picture_conference);
+        if (mApplication.phone.getPhoneName().equals("CDMA")) {
+            // This state corresponds to both 3-Way merged call and
+            // Call Waiting accepted call.
+            // Display only the "dialing" icon and no caller information cause in CDMA
+            // as in this state the user does not really know which caller party he is talking to.
+            showImage(mPhoto, R.drawable.picture_dialing);
+            mName.setText(R.string.card_title_in_call);
+        } else {
+            // Display the "conference call" image in the photo slot,
+            // with no other information.
+            showImage(mPhoto, R.drawable.picture_conference);
+            mName.setText(R.string.card_title_conf_call);
+        }
 
-        mName.setText(R.string.card_title_conf_call);
         mName.setVisibility(View.VISIBLE);
 
         // TODO: For a conference call, the "phone number" slot is specced
@@ -1178,7 +1267,13 @@ public class CallCard extends FrameLayout
                 // look for the photoResource if it is available.
                 CallerInfo ci = null;
                 {
-                    Connection conn = call.getEarliestConnection();
+                    Connection conn = null;
+                    if (mApplication.phone.getPhoneName().equals("CDMA")) {
+                        conn = call.getLatestConnection();
+                    } else { // GSM.
+                        conn = call.getEarliestConnection();
+                    }
+
                     if (conn != null) {
                         Object o = conn.getUserData();
                         if (o instanceof CallerInfo) {
@@ -1358,7 +1453,7 @@ public class CallCard extends FrameLayout
 
         int bluetoothIconId = 0;
         if (((state == Call.State.INCOMING) || (state == Call.State.WAITING))
-                && PhoneApp.getInstance().showBluetoothIndication()) {
+                && mApplication.showBluetoothIndication()) {
             // Display the special bluetooth icon also, if this is an incoming
             // call and the audio will be routed to bluetooth.
             bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
old mode 100644 (file)
new mode 100755 (executable)
index edb00cd..8448218
@@ -19,6 +19,10 @@ package com.android.phone;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+import com.android.internal.telephony.cdma.SignalToneUtil;
+import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
+import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.gsm.GSMPhone;
@@ -61,6 +65,18 @@ public class CallNotifier extends Handler
     // before giving up and falling back to the default ringtone.
     private static final int RINGTONE_QUERY_WAIT_TIME = 500;  // msec
 
+    // Timers related to CDMA Call Waiting
+    // 1) For displaying Caller Info
+    // 2) For disabling "Add Call" menu option once User selects Ignore or CW Timeout occures
+    private static final int CALLWAITING_CALLERINFO_DISPLAY_TIME = 20000; // msec
+    private static final int CALLWAITING_ADDCALL_DISABLE_TIME = 60000; // msec
+
+    // Time to display the  DisplayInfo Record sent by CDMA network
+    private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
+
+    // Boolean to store information if a Call Waiting timed out
+    private boolean mCallWaitingTimeOut = false;
+
     // values used to track the query state
     private static final int CALLERINFO_QUERY_READY = 0;
     private static final int CALLERINFO_QUERYING = -1;
@@ -80,10 +96,15 @@ public class CallNotifier extends Handler
     private static final int PHONE_DISCONNECT = 3;
     private static final int PHONE_UNKNOWN_CONNECTION_APPEARED = 4;
     private static final int PHONE_INCOMING_RING = 5;
-    private static final int PHONE_CDMA_CALL_WAITING = 6;
+    private static final int PHONE_STATE_DISPLAYINFO = 6;
+    private static final int PHONE_STATE_SIGNALINFO = 7;
+    private static final int PHONE_CDMA_CALL_WAITING = 8;
     // Events generated internally:
-    private static final int PHONE_MWI_CHANGED = 7;
-    private static final int PHONE_BATTERY_LOW = 8;
+    private static final int PHONE_MWI_CHANGED = 9;
+    private static final int PHONE_BATTERY_LOW = 10;
+    private static final int CALLWAITING_CALLERINFO_DISPLAY_DONE = 11;
+    private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 12;
+    private static final int DISPLAYINFO_NOTIFICATION_DONE = 13;
 
     private PhoneApp mApplication;
     private Phone mPhone;
@@ -91,6 +112,12 @@ public class CallNotifier extends Handler
     private BluetoothHandsfree mBluetoothHandsfree;
     private boolean mSilentRingerRequested;
 
+    // ToneGenerator instance for playing SignalInfo tones
+    private ToneGenerator mSignalInfoToneGenerator;
+
+    // The tone volume relative to other sounds in the stream SignalInfo
+    private static final int TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO = 50;
+
     public CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
                         BluetoothHandsfree btMgr) {
         mApplication = app;
@@ -101,7 +128,27 @@ public class CallNotifier extends Handler
         mPhone.registerForDisconnect(this, PHONE_DISCONNECT, null);
         mPhone.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
         mPhone.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
-        mPhone.registerForCallWaiting(this,PHONE_CDMA_CALL_WAITING, null);
+
+        if (mPhone.getPhoneName().equals("CDMA")) {
+            if (DBG) log("Registering for Call Waiting, Signal and Display Info.");
+            mPhone.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
+            mPhone.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
+            mPhone.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
+
+            // Instantiate the ToneGenerator for SignalInfo and CallWaiting
+            // TODO(Moto): We probably dont need the mSignalInfoToneGenerator instance
+            // around forever. Need to change it so as to create a ToneGenerator instance only
+            // when a tone is being played and releases it after its done playing.
+            try {
+                mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION,
+                        TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO);
+            } catch (RuntimeException e) {
+                Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
+                        "mSignalInfoToneGenerator: " + e);
+                mSignalInfoToneGenerator = null;
+            }
+        }
+
         mRinger = ringer;
         mBluetoothHandsfree = btMgr;
 
@@ -133,10 +180,7 @@ public class CallNotifier extends Handler
                     if (DBG) log("RING before NEW_RING, skipping");
                 }
                 break;
-            case PHONE_CDMA_CALL_WAITING:
-                if (DBG) log("CDMA CALL WAITING... ");
-                onCdmaCallwaiting((AsyncResult) msg.obj);
-                break;
+
             case PHONE_STATE_CHANGED:
                 onPhoneStateChanged((AsyncResult) msg.obj);
                 break;
@@ -171,6 +215,38 @@ public class CallNotifier extends Handler
                 onBatteryLow();
                 break;
 
+            case PHONE_CDMA_CALL_WAITING:
+                if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
+                onCdmaCallWaiting((AsyncResult) msg.obj);
+                break;
+
+            case CALLWAITING_CALLERINFO_DISPLAY_DONE:
+                if (DBG) log("Received CALLWAITING_CALLERINFO_DISPLAY_DONE event ...");
+                mCallWaitingTimeOut = true;
+                onCdmaCallWaitingReject();
+                break;
+
+            case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
+                if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
+                // Set the mAddCallMenuStateAfterCW state to true
+                mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
+                break;
+
+            case PHONE_STATE_DISPLAYINFO:
+                if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
+                onDisplayInfo((AsyncResult) msg.obj);
+                break;
+
+            case PHONE_STATE_SIGNALINFO:
+                if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
+                onSignalInfo((AsyncResult) msg.obj);
+                break;
+
+            case DISPLAYINFO_NOTIFICATION_DONE:
+                if (DBG) log("Received Display Info notification done event ...");
+                CdmaDisplayInfo.dismissDisplayInfoRecord();
+                break;
+
             default:
                 // super.handleMessage(msg);
         }
@@ -262,10 +338,6 @@ public class CallNotifier extends Handler
         if (VDBG) log("- onNewRingingConnection() done.");
     }
 
-    private void onCdmaCallwaiting(AsyncResult r) {
-       return;
-    }
-
     /**
      * Helper method to manage the start of incoming call queries
      */
@@ -447,6 +519,14 @@ public class CallNotifier extends Handler
         mPhone.unregisterForDisconnect(this);
         mPhone.unregisterForUnknownConnection(this);
         mPhone.unregisterForIncomingRing(this);
+        mPhone.unregisterForCallWaiting(this);
+        mPhone.unregisterForDisplayInfo(this);
+        mPhone.unregisterForSignalInfo(this);
+
+        //Release the ToneGenerator used for playing SignalInfo and CallWaiting
+        if (mSignalInfoToneGenerator != null) {
+            mSignalInfoToneGenerator.release();
+        }
 
         //Register all events new to the new active phone
         mPhone.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
@@ -454,6 +534,22 @@ public class CallNotifier extends Handler
         mPhone.registerForDisconnect(this, PHONE_DISCONNECT, null);
         mPhone.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
         mPhone.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
+        if (mPhone.getPhoneName().equals("CDMA")) {
+            if (DBG) log("Registering for Call Waiting, Signal and Display Info.");
+            mPhone.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
+            mPhone.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
+            mPhone.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
+
+            // Instantiate the ToneGenerator for SignalInfo
+            try {
+                mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION,
+                        TONE_RELATIVE_VOLUME_LOPRI_SIGNALINFO);
+            } catch (RuntimeException e) {
+                Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
+                        "mSignalInfoToneGenerator: " + e);
+                mSignalInfoToneGenerator = null;
+            }
+        }
     }
 
     /**
@@ -509,6 +605,11 @@ public class CallNotifier extends Handler
             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
         }
 
+        if (mPhone.getPhoneName().equals("CDMA")) {
+            // Create the SignalInfo tone player to stop any signalInfo tone being played.
+            new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
+        }
+
         Connection c = (Connection) r.result;
         if (DBG && c != null) {
             log("- onDisconnect: cause = " + c.getDisconnectCause()
@@ -681,6 +782,15 @@ public class CallNotifier extends Handler
                 if (VDBG) log("- phone still in use; not releasing wake locks.");
             }
         }
+
+        if (mPhone.getPhoneName().equals("CDMA")) {
+            // Resetting the CdmaPhoneCallState members
+            mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
+
+            // Remove Call waiting timers
+            removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
+            removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
+        }
     }
 
     /**
@@ -898,6 +1008,171 @@ public class CallNotifier extends Handler
         }
     }
 
+    /**
+     * Displays a notification when the phone receives a DisplayInfo record.
+     */
+    private void onDisplayInfo(AsyncResult r) {
+        // Extract the DisplayInfo String from the message
+        CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
+
+        if (displayInfoRec != null) {
+            String displayInfo = displayInfoRec.alpha;
+            if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
+            CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
+
+            // start a 2 second timer
+            sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
+                    DISPLAYINFO_NOTIFICATION_TIME);
+        }
+    }
+
+    /**
+     * Helper class to play SignalInfo tones using the ToneGenerator.
+     *
+     * To use, just instantiate a new SignalInfoTonePlayer
+     * (passing in the ToneID constant for the tone you want)
+     * and start() it.
+     */
+    private class SignalInfoTonePlayer extends Thread {
+        private int mToneId;
+
+        SignalInfoTonePlayer(int toneId) {
+            super();
+            mToneId = toneId;
+        }
+
+        @Override
+        public void run() {
+            if (DBG) log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
+
+            if (mSignalInfoToneGenerator != null) {
+                //First stop any ongoing SignalInfo tone
+                mSignalInfoToneGenerator.stopTone();
+
+                //Start playing the new tone if its a valid tone
+                mSignalInfoToneGenerator.startTone(mToneId);
+            }
+        }
+    }
+
+    /**
+     * Plays a tone when the phone receives a SignalInfo record.
+     */
+    private void onSignalInfo(AsyncResult r) {
+        // Extract the SignalInfo String from the message
+        CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
+        // Only proceed if a Signal info is present.
+        if (signalInfoRec != null) {
+            boolean isPresent = signalInfoRec.isPresent;
+            if (DBG) log("onSignalInfo: isPresent=" + isPresent);
+            if (isPresent) {// if tone is valid
+                int uSignalType = signalInfoRec.signalType;
+                int uAlertPitch = signalInfoRec.alertPitch;
+                int uSignal = signalInfoRec.signal;
+
+                if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
+                        uAlertPitch + ", uSignal=" + uSignal);
+                //Map the Signal to a ToneGenerator ToneID only if Signal info is present
+                int toneID =
+                        SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
+
+                //Create the SignalInfo tone player and pass the ToneID
+                new SignalInfoTonePlayer(toneID).start();
+            }
+        }
+    }
+
+    /**
+     * Plays a Call waiting tone if it is present in the second incoming call.
+     */
+    private void onCdmaCallWaiting(AsyncResult r) {
+        // Start the InCallScreen Activity if its not on foreground
+        if (!mApplication.isShowingCallScreen()) {
+            PhoneUtils.showIncomingCallUi();
+        }
+
+        // Start timer for CW display
+        mCallWaitingTimeOut = false;
+        sendEmptyMessageDelayed(CALLWAITING_CALLERINFO_DISPLAY_DONE,
+                CALLWAITING_CALLERINFO_DISPLAY_TIME);
+
+        // Set the mAddCallMenuStateAfterCW state to false
+        mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(false);
+
+        // Start the timer for disabling "Add Call" menu option
+        sendEmptyMessageDelayed(CALLWAITING_ADDCALL_DISABLE_TIMEOUT,
+                CALLWAITING_ADDCALL_DISABLE_TIME);
+
+        // Extract the Call waiting information
+        CdmaCallWaitingNotification infoCW = (CdmaCallWaitingNotification) r.result;
+        int isPresent = infoCW.isPresent;
+        if (DBG) log("onCdmaCallWaiting: isPresent=" + isPresent);
+        if (isPresent == 1 ) {//'1' if tone is valid
+            int uSignalType = infoCW.signalType;
+            int uAlertPitch = infoCW.alertPitch;
+            int uSignal = infoCW.signal;
+            if (DBG) log("onCdmaCallWaiting: uSignalType=" + uSignalType + ", uAlertPitch="
+                    + uAlertPitch + ", uSignal=" + uSignal);
+            //Map the Signal to a ToneGenerator ToneID only if Signal info is present
+            int toneID =
+                SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
+
+            //Create the SignalInfo tone player and pass the ToneID
+            new SignalInfoTonePlayer(toneID).start();
+        }
+    }
+
+    /**
+     * Performs Call logging based on Timeout or Ignore Call Waiting Call for CDMA,
+     * and finally calls Hangup on the Call Waiting connection.
+     */
+    /* package */ void onCdmaCallWaitingReject() {
+        final Call ringingCall = mPhone.getRingingCall();
+
+        // Call waiting timeout scenario
+        if (ringingCall.getState() == Call.State.WAITING) {
+            // Code for perform Call logging and missed call notification
+            Connection c = ringingCall.getLatestConnection();
+
+            if (c != null) {
+                String number = c.getAddress();
+                int isPrivateNumber = c.getNumberPresentation();
+                long date = c.getCreateTime();
+                long duration = c.getDurationMillis();
+                int callLogType = mCallWaitingTimeOut ?
+                        CallLog.Calls.MISSED_TYPE  : CallLog.Calls.INCOMING_TYPE;
+
+                // get the callerinfo object and then log the call with it.
+                Object o = c.getUserData();
+                CallerInfo ci;
+                if ((o == null) || (o instanceof CallerInfo)) {
+                    ci = (CallerInfo) o;
+                } else {
+                    ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+                }
+
+                // Add Call log
+                Calls.addCall(ci, mApplication, number, isPrivateNumber, callLogType,
+                        date, (int) duration / 1000);
+
+                if (callLogType == CallLog.Calls.MISSED_TYPE) {
+                    // Add missed call notification
+                    NotificationMgr.getDefault().notifyMissedCall(ci.name,
+                            ci.phoneNumber, ci.phoneLabel, date);
+                }
+
+                // Set the Phone Call State to SINGLE_ACTIVE as there is only one connection
+                mApplication.cdmaPhoneCallState.setCurrentCallState(
+                        CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+
+                // Hangup the RingingCall connection for CW
+                PhoneUtils.hangup(c);
+            }
+
+            //Reset the mCallWaitingTimeOut boolean
+            mCallWaitingTimeOut = false;
+        }
+    }
 
     private void log(String msg) {
         Log.d(LOG_TAG, msg);
diff --git a/src/com/android/phone/CdmaDisplayInfo.java b/src/com/android/phone/CdmaDisplayInfo.java
new file mode 100755 (executable)
index 0000000..1e7dee8
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Helper class for displaying the DisplayInfo sent by CDMA network.
+ */
+public class CdmaDisplayInfo {
+    private static final String LOG_TAG = "CdmaDisplayInfo";
+    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /** CDMA DisplayInfo dialog */
+    private static AlertDialog sDisplayInfoDialog = null;
+
+    /**
+     * Handle the DisplayInfo record and display the alert dialog with
+     * the network message.
+     *
+     * @param context context to get strings.
+     * @param infoMsg Text message from Network.
+     */
+    public static void displayInfoRecord(Context context, String infoMsg) {
+
+        if (DBG) log("displayInfoRecord: infoMsg=" + infoMsg);
+
+        if (sDisplayInfoDialog != null) {
+            sDisplayInfoDialog.dismiss();
+        }
+
+        // displaying system alert dialog on the screen instead of
+        // using another activity to display the message.  This
+        // places the message at the forefront of the UI.
+        sDisplayInfoDialog = new AlertDialog.Builder(context)
+                .setIcon(android.R.drawable.ic_dialog_info)
+                .setTitle(context.getText(R.string.network_message))
+                .setMessage(infoMsg)
+                .setCancelable(true)
+                .create();
+
+        sDisplayInfoDialog.getWindow().setType(
+                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        sDisplayInfoDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        sDisplayInfoDialog.show();
+        PhoneApp.getInstance().wakeUpScreen();
+
+    }
+
+    /**
+     * Dismiss the DisplayInfo record
+     */
+    public static void dismissDisplayInfoRecord() {
+
+        if (DBG) log("Dissmissing Display Info Record...");
+
+        if (sDisplayInfoDialog != null) {
+            sDisplayInfoDialog.dismiss();
+            sDisplayInfoDialog = null;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, "[CdmaDisplayInfo] " + msg);
+    }
+}
diff --git a/src/com/android/phone/CdmaPhoneCallState.java b/src/com/android/phone/CdmaPhoneCallState.java
new file mode 100755 (executable)
index 0000000..a26baaa
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+/**
+ * Class to internally keep track of Call states to maintain
+ * information for Call Waiting and 3Way for CDMA instance of Phone App.
+ *
+ * Explaination for PhoneApp's Call states and why it is required:
+ * IDLE - When no call is going on. This is just required as default state to reset the PhoneApp
+ *        call state to when the complete call gets disconnected
+ * SINGLE_ACTIVE - When only single call is active.
+ *        In normal case(on a single call) this state would be similar for FW's state of ACTIVE
+ *        call or phone state of OFFHOOK, but in more complex conditions e.g. when phone is already
+ *        in a CONF_CALL state and user rejects a CW, which basically tells the PhoneApp that the
+ *        Call is back to a single call, the FW's state still would remain ACTIVE or OFFHOOK and
+ *        isGeneric would still be true. At this condition PhoneApp does need to enable the
+ *        "Add Call" menu item and disable the "Swap" and "Merge" options
+ * THRWAY_ACTIVE - When user initiate an outgoing call when already on a call.
+ *        fgCall can have more than one connections from various scenarios (accepting the CW or
+ *        making a 3way call) but once we are in this state and one of the parties drops off,
+ *        when the user originates another call we need to remember this state to update the menu
+ *        items accordingly. FW currently does not differentiate this condition hence PhoneApp
+ *        needs to maintain it.
+ * CONF_CALL - When the user merges two calls or on accepting the Call waiting call.
+ *        This is required cause even though a call might be generic but that does not mean it is
+ *        in conference. We can take the same example mention in the SINGLE_ACTIVE state.
+ *
+ * TODO(Moto): Eventually this state information should be maintained by Telephony FW.
+ */
+   public class CdmaPhoneCallState {
+
+        /**
+         * Allowable values for the PhoneCallState.
+         *   IDLE - When no call is going on.
+         *   SINGLE_ACTIVE - When only single call is active
+         *   THRWAY_ACTIVE - When user initiate an outgoing call when already on a call
+         *   CONF_CALL - When the user merges two calls or on accepting the Call waiting call
+         */
+        public enum PhoneCallState {
+            IDLE,
+            SINGLE_ACTIVE,
+            THRWAY_ACTIVE,
+            CONF_CALL
+        }
+
+        // For storing current and previous PhoneCallState's
+        private PhoneCallState mPreviousCallState;
+        private PhoneCallState mCurrentCallState;
+
+        // Boolean to track 3Way display state
+        private boolean mThreeWayCallOrigStateDialing;
+
+        // Flag to indicate if the "Add Call" menu item in an InCallScreen is OK
+        // to be displayed after a Call Waiting call was ignored or timed out
+        private boolean mAddCallMenuStateAfterCW;
+
+        /**
+         * Initialize PhoneCallState related members - constructor
+         */
+        public void CdmaPhoneCallStateInit() {
+            mCurrentCallState = PhoneCallState.IDLE;
+            mPreviousCallState = PhoneCallState.IDLE;
+            mThreeWayCallOrigStateDialing = false;
+            mAddCallMenuStateAfterCW = true;
+        }
+
+        /**
+         * Returns the current call state
+         */
+        public PhoneCallState getCurrentCallState() {
+            return mCurrentCallState;
+        }
+
+        /**
+         * Set current and previous PhoneCallState's
+         */
+        public void setCurrentCallState(PhoneCallState newState) {
+            mPreviousCallState = mCurrentCallState;
+            mCurrentCallState = newState;
+
+            //Reset the 3Way display boolean
+            mThreeWayCallOrigStateDialing = false;
+
+            //Set mAddCallMenuStateAfterCW to true
+            //if the current state is being set to SINGLE_ACTIVE
+            //and previous state was IDLE as we could reach the SINGLE_ACTIVE
+            //from CW ignore too. For all other cases let the timer or
+            //specific calls to setAddCallMenuStateAfterCallWaiting set
+            //mAddCallMenuStateAfterCW.
+            if ((mCurrentCallState == PhoneCallState.SINGLE_ACTIVE)
+                && (mPreviousCallState == PhoneCallState.IDLE)) {
+                mAddCallMenuStateAfterCW = true;
+            }
+        }
+
+        /**
+         * Return 3Way display information
+         */
+        public boolean IsThreeWayCallOrigStateDialing() {
+            return mThreeWayCallOrigStateDialing;
+        }
+
+        /**
+         * Set 3Way display information
+         */
+        public void setThreeWayCallOrigState(boolean newState) {
+            mThreeWayCallOrigStateDialing = newState;
+        }
+
+        /**
+         * Return information for enabling/disabling "Add Call" menu item
+         */
+        public boolean getAddCallMenuStateAfterCallWaiting() {
+            return mAddCallMenuStateAfterCW;
+        }
+
+        /**
+         * Set mAddCallMenuStateAfterCW to enabling/disabling "Add Call" menu item
+         */
+        public void setAddCallMenuStateAfterCallWaiting(boolean newState) {
+            mAddCallMenuStateAfterCW = newState;
+        }
+
+        /**
+         * Return previous PhoneCallState's
+         */
+        public PhoneCallState getPreviousCallState() {
+            return mPreviousCallState;
+        }
+
+        /**
+         * Reset all PhoneCallState
+         */
+        public void resetCdmaPhoneCallState() {
+            mCurrentCallState = PhoneCallState.IDLE;
+            mPreviousCallState = PhoneCallState.IDLE;
+            mThreeWayCallOrigStateDialing = false;
+            mAddCallMenuStateAfterCW = true;
+        }
+   }
old mode 100644 (file)
new mode 100755 (executable)
index b724ad0..b12f7df
@@ -66,6 +66,8 @@ class InCallMenu {
     InCallMenuItemView mHold;
     InCallMenuItemView mAnswerAndHold;
     InCallMenuItemView mAnswerAndEnd;
+    InCallMenuItemView mAnswer;
+    InCallMenuItemView mIgnore;
 
     InCallMenu(InCallScreen inCallScreen) {
         if (DBG) log("InCallMenu constructor...");
@@ -185,6 +187,16 @@ class InCallMenu {
         mAnswerAndEnd.setOnClickListener(mInCallScreen);
         mAnswerAndEnd.setText(R.string.menu_answerAndEnd);
 
+        mAnswer = new InCallMenuItemView(wrappedContext);
+        mAnswer.setId(R.id.menuAnswer);
+        mAnswer.setOnClickListener(mInCallScreen);
+        mAnswer.setText(R.string.menu_answer);
+
+        mIgnore = new InCallMenuItemView(wrappedContext);
+        mIgnore.setId(R.id.menuIgnore);
+        mIgnore.setOnClickListener(mInCallScreen);
+        mIgnore.setText(R.string.menu_ignore);
+
         //
         // Load all the items into the correct "slots" in the InCallMenuView.
         //
@@ -201,7 +213,11 @@ class InCallMenu {
         // Row 0:
         // This usually has "Show/Hide dialpad", but that gets replaced by
         // "Manage conference" if a conference call is active.
-        mInCallMenuView.addItemView(mManageConference, 0);
+        PhoneApp app = PhoneApp.getInstance();
+        // As managing conference is only valid for GSM and not for CDMA
+        if (app.phone.getPhoneName().equals("GSM")) {
+            mInCallMenuView.addItemView(mManageConference, 0);
+        }
         mInCallMenuView.addItemView(mShowDialpad, 0);
 
         // Row 1:
@@ -213,12 +229,18 @@ class InCallMenu {
         // Row 2:
         // In this row we see *either*  bluetooth/speaker/mute/hold
         // *or* answerAndHold/answerAndEnd, but never all 6 together.
-        mInCallMenuView.addItemView(mHold, 2);
+        // For CDMA only Answer or Ignore option is valid for a Call Waiting scenario
+        if (app.phone.getPhoneName().equals("CDMA")) {
+            mInCallMenuView.addItemView(mAnswer, 2);
+            mInCallMenuView.addItemView(mIgnore, 2);
+        } else {
+            mInCallMenuView.addItemView(mHold, 2);
+            mInCallMenuView.addItemView(mAnswerAndHold, 2);
+            mInCallMenuView.addItemView(mAnswerAndEnd, 2);
+        }
         mInCallMenuView.addItemView(mMute, 2);
         mInCallMenuView.addItemView(mSpeaker, 2);
         mInCallMenuView.addItemView(mBluetooth, 2);
-        mInCallMenuView.addItemView(mAnswerAndHold, 2);
-        mInCallMenuView.addItemView(mAnswerAndEnd, 2);
 
         mInCallMenuView.dumpState();
     }
@@ -257,12 +279,29 @@ class InCallMenu {
             // TODO: be sure to test this for "only one line in use and it's
             // active" AND for "only one line in use and it's on hold".
             if (hasActiveCall && !hasHoldingCall) {
-                mAnswerAndHold.setVisible(true);
-                mAnswerAndHold.setEnabled(true);
-                mAnswerAndEnd.setVisible(true);
-                mAnswerAndEnd.setEnabled(true);
+                // For CDMA only make "Answer" and "Ignore" visible
+                if (phone.getPhoneName().equals("CDMA")) {
+                    mAnswer.setVisible(true);
+                    mAnswer.setEnabled(true);
+                    mIgnore.setVisible(true);
+                    mIgnore.setEnabled(true);
+
+                    // Explicitly remove GSM menu items
+                    mAnswerAndHold.setVisible(false);
+                    mAnswerAndEnd.setVisible(false);
+                } else {
+                    mAnswerAndHold.setVisible(true);
+                    mAnswerAndHold.setEnabled(true);
+                    mAnswerAndEnd.setVisible(true);
+                    mAnswerAndEnd.setEnabled(true);
+
+                    // Explicitly remove CDMA menu items
+                    mAnswer.setVisible(false);
+                    mIgnore.setVisible(false);
+
+                    mManageConference.setVisible(false);
+                }
 
-                mManageConference.setVisible(false);
                 mShowDialpad.setVisible(false);
                 mEndCall.setVisible(false);
                 mAddCall.setVisible(false);
@@ -371,6 +410,13 @@ class InCallMenu {
         mHold.setIndicatorState(onHold);
         mHold.setEnabled(canHold);
 
+        // In CDMA "Answer" and "Ignore" are only useful
+        // when there's an incoming ringing call (Callwaiting)
+        mAnswer.setVisible(false);
+        mAnswer.setEnabled(false);
+        mIgnore.setVisible(false);
+        mIgnore.setEnabled(false);
+
         // "Answer & end" and "Answer & hold" are only useful
         // when there's an incoming ringing call (see above.)
         mAnswerAndHold.setVisible(false);
old mode 100644 (file)
new mode 100755 (executable)
index 401ef97..300b406
@@ -43,6 +43,8 @@ import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.provider.Checkin;
 import android.provider.Settings;
 import android.telephony.PhoneNumberUtils;
@@ -122,6 +124,9 @@ public class InCallScreen extends Activity
     // The "touch lock" overlay timeout comes from Gservices; this is the default.
     private static final int TOUCH_LOCK_DELAY_DEFAULT =  6000;  // msec
 
+    // Amount of time for Displaying "Dialing" for 3way Calling origination
+    private static final int THREEWAY_CALLERINFO_DISPLAY_TIME = 2000; // msec
+
     // See CallTracker.MAX_CONNECTIONS_PER_CALL
     private static final int MAX_CALLERS_IN_CONFERENCE = 5;
 
@@ -140,6 +145,8 @@ public class InCallScreen extends Activity
     private static final int ALLOW_SCREEN_ON = 112;
     private static final int TOUCH_LOCK_TIMER = 113;
     private static final int BLUETOOTH_STATE_CHANGED = 114;
+    private static final int PHONE_CDMA_CALL_WAITING = 115;
+    private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 116;
 
 
     // High-level "modes" of the in-call UI.
@@ -270,6 +277,7 @@ public class InCallScreen extends Activity
                 // foreground...
             }
 
+            PhoneApp app = PhoneApp.getInstance();
             switch (msg.what) {
                 case SUPP_SERVICE_FAILED:
                     onSuppServiceFailed((AsyncResult) msg.obj);
@@ -321,7 +329,7 @@ public class InCallScreen extends Activity
                     MmiCode mmiCode = (MmiCode) ((AsyncResult) msg.obj).result;
                     // if phone is a CDMA phone display feature code completed message
                     if (mPhone.getPhoneName().equals("CDMA")) {
-                        PhoneUtils.displayMMIComplete(mPhone, PhoneApp.getInstance(), mmiCode, null, null);
+                        PhoneUtils.displayMMIComplete(mPhone, app, mmiCode, null, null);
                     } else {
                         if (mmiCode.getState() != MmiCode.State.PENDING) {
                             if (DBG) log("Got MMI_COMPLETE, finishing...");
@@ -357,7 +365,7 @@ public class InCallScreen extends Activity
                     // (Note this will cause the screen to turn on
                     // immediately, if it's currently off because of a
                     // prior preventScreenOn(true) call.)
-                    PhoneApp.getInstance().preventScreenOn(false);
+                    app.preventScreenOn(false);
                     break;
 
                 case TOUCH_LOCK_TIMER:
@@ -374,6 +382,31 @@ public class InCallScreen extends Activity
                     // directly from PhoneApp.showBluetoothIndication().)
                     updateScreen();
                     break;
+
+                case PHONE_CDMA_CALL_WAITING:
+                    if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
+                    Connection cn = mRingingCall.getLatestConnection();
+
+                    // Only proceed if we get a valid connection object
+                    if (cn != null) {
+                        // Finally update screen with Call waiting info and request
+                        // screen to wake up
+                        updateScreen();
+                        app.updateWakeState();
+                    }
+                    break;
+
+                case THREEWAY_CALLERINFO_DISPLAY_DONE:
+                    if (DBG) log("Received THREEWAY_CALLERINFO_DISPLAY_DONE event ...");
+                    if (app.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                        // Set the mThreeWayCallOrigStateDialing state to true
+                        app.cdmaPhoneCallState.setThreeWayCallOrigState(false);
+
+                        //Finally update screen with with the current on going call
+                        updateScreen();
+                    }
+                    break;
             }
         }
     };
@@ -826,14 +859,19 @@ public class InCallScreen extends Activity
         if (!mRegisteredForPhoneStates) {
             mPhone.registerForPhoneStateChanged(mHandler, PHONE_STATE_CHANGED, null);
             mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
-            mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
-
-            // register for the MMI complete message.  Upon completion,
-            // PhoneUtils will bring up a system dialog instead of the
-            // message display class in PhoneUtils.displayMMIComplete().
-            // We'll listen for that message too, so that we can finish
-            // the activity at the same time.
-            mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
+            if (mPhone.getPhoneName().equals("GSM")) {
+                mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
+
+                // register for the MMI complete message.  Upon completion,
+                // PhoneUtils will bring up a system dialog instead of the
+                // message display class in PhoneUtils.displayMMIComplete().
+                // We'll listen for that message too, so that we can finish
+                // the activity at the same time.
+                mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
+            } else { // CDMA
+                if (DBG) log("Registering for Call Waiting.");
+                mPhone.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
+            }
 
             mPhone.setOnPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
             mPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
@@ -845,6 +883,7 @@ public class InCallScreen extends Activity
         mPhone.unregisterForPhoneStateChanged(mHandler);
         mPhone.unregisterForDisconnect(mHandler);
         mPhone.unregisterForMmiInitiate(mHandler);
+        mPhone.unregisterForCallWaiting(mHandler);
         mPhone.setOnPostDialCharacter(null, POST_ON_DIAL_CHARS, null);
         mRegisteredForPhoneStates = false;
     }
@@ -1049,23 +1088,29 @@ public class InCallScreen extends Activity
         if (mPhone.getPhoneName().equals("CDMA")) {
             // The green CALL button means either "Answer", "Swap calls/On Hold", or
             // "Add to 3WC", depending on the current state of the Phone.
+
+            PhoneApp app = PhoneApp.getInstance();
+            CdmaPhoneCallState.PhoneCallState currCallState =
+                app.cdmaPhoneCallState.getCurrentCallState();
             if (hasRingingCall) {
-                if (VDBG) log("handleCallKey: ringing ==> answer!");
+                //Scenario 1: Accepting the First Incoming and Call Waiting call
+                if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
                 internalAnswerCall();  // Automatically holds the current active call,
                                        // if there is one
-            } else {
-                // On a CDMA phone, if there's no ringing call, CALL means
-                // "flash" in any context.  (Depending on the state of the
-                // network, this could mean "hold", "swap calls", or
-                // "merge into 3-way call".)
-
-                // TODO: It's ugly to call switchHoldingAndActive() here,
-                // since we're not *really* trying to swap calls.  (We just
-                // want to send a flash command.)  Instead, consider having
-                // the telephony layer provide an explicit "flash" API.
+            } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                    && (hasActiveCall)) {
+                //Scenario 2: Merging 3Way calls
+                if (DBG) log("answerCall: Merge 3-way call scenario");
+                // Merge calls
+                PhoneUtils.mergeCalls(mPhone);
+            } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                //Scenario 3: Switching between two Call waiting calls or drop the latest
+                // connection if in a 3Way merge scenario
+                if (DBG) log("answerCall: Switch btwn 2 calls scenario");
+                // Send flash cmd
                 PhoneUtils.switchHoldingAndActive(mPhone);
             }
-        } else {
+        } else { // GSM.
             if (hasRingingCall) {
                 // If an incoming call is ringing, the CALL button is actually
                 // handled by the PhoneWindowManager.  (We do this to make
@@ -1614,6 +1659,9 @@ public class InCallScreen extends Activity
             mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
                                              callEndedDisplayDelay);
         }
+
+        // Remove 3way timer (only meaningful for CDMA)
+        mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
     }
 
     /**
@@ -2044,6 +2092,26 @@ public class InCallScreen extends Activity
                 // onPhoneStateChanged().
                 mDialer.clearDigits();
 
+                PhoneApp app = PhoneApp.getInstance();
+                if (app.phone.getPhoneName().equals("CDMA")) {
+                    // Start the 2 second timer for 3 Way CallerInfo
+                    if (app.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                        //Unmute for the second MO call
+                        PhoneUtils.setMuteInternal(mPhone, false);
+
+                        //Start the timer for displaying "Dialing" for second call
+                        Message msg = Message.obtain(mHandler, THREEWAY_CALLERINFO_DISPLAY_DONE);
+                        mHandler.sendMessageDelayed(msg, THREEWAY_CALLERINFO_DISPLAY_TIME);
+
+                        // Set the mThreeWayCallOrigStateDialing state to true
+                        app.cdmaPhoneCallState.setThreeWayCallOrigState(true);
+
+                        //Update screen to show 3way dialing
+                        updateScreen();
+                    }
+                }
+
                 return InCallInitStatus.SUCCESS;
             case PhoneUtils.CALL_STATUS_DIALED_MMI:
                 if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
@@ -2270,6 +2338,17 @@ public class InCallScreen extends Activity
                 internalAnswerAndEnd();
                 break;
 
+            case R.id.menuAnswer:
+                if (DBG) log("onClick: Answer...");
+                internalAnswerCall();
+                break;
+
+            case R.id.menuIgnore:
+                if (DBG) log("onClick: Ignore...");
+                final CallNotifier notifier = PhoneApp.getInstance().notifier;
+                notifier.onCdmaCallWaitingReject();
+                break;
+
             case R.id.menuSwapCalls:
                 if (VDBG) log("onClick: SwapCalls...");
                 internalSwapCalls();
old mode 100644 (file)
new mode 100755 (executable)
index 20fec32..61d3ded
@@ -141,6 +141,9 @@ public class PhoneApp extends Application {
     int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_ERROR;
     boolean mShowBluetoothIndication = false;
 
+    // Internal PhoneApp Call state tracker
+    CdmaPhoneCallState cdmaPhoneCallState;
+
     // The InCallScreen instance (or null if the InCallScreen hasn't been
     // created yet.)
     private InCallScreen mInCallScreen;
@@ -419,9 +422,15 @@ public class PhoneApp extends Application {
         // start with the default value to set the mute state.
         mShouldRestoreMuteOnInCallResume = false;
 
-        //Register for Cdma Information Records
-// TODO(Moto): Merge
-//        phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);
+        // Register for Cdma Information Records
+        // TODO(Moto): Merge
+        // phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);
+
+        if (phone.getPhoneName().equals("CDMA")) {
+            // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+            cdmaPhoneCallState = new CdmaPhoneCallState();
+            cdmaPhoneCallState.CdmaPhoneCallStateInit();
+        }
    }
 
     /**
@@ -931,6 +940,12 @@ public class PhoneApp extends Application {
             sim.registerForLocked(mHandler, EVENT_SIM_LOCKED, null);
             sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null);
         }
+
+        if (phone.getPhoneName().equals("CDMA")) {
+            // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+            cdmaPhoneCallState = new CdmaPhoneCallState();
+            cdmaPhoneCallState.CdmaPhoneCallStateInit();
+        }
     }
 
 
old mode 100644 (file)
new mode 100755 (executable)
index 7648948..af89636
@@ -226,6 +226,21 @@ public class PhoneUtils {
                 phone.acceptCall();
                 answered = true;
                 setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
+                if (phone.getPhoneName().equals("CDMA")) {
+                    PhoneApp app = PhoneApp.getInstance();
+                    if (app.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.IDLE) {
+                        // This is the FIRST incoming call being answered.
+                        // Set the Phone Call State to SINGLE_ACTIVE
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+                    } else {
+                        // This is the CALL WAITING call being answered.
+                        // Set the Phone Call State to CONF_CALL
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+                    }
+                }
             } catch (CallStateException ex) {
                 Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
             }
@@ -358,6 +373,21 @@ public class PhoneUtils {
                 }
 
             } else {
+                PhoneApp app = PhoneApp.getInstance();
+
+                if (phone.getPhoneName().equals("CDMA")){
+                    if (app.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.IDLE) {
+                        // This is the first outgoing call. Set the Phone Call State to ACTIVE
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+                    } else {
+                        // This is the second outgoing call. Set the Phone Call State to 3WAY
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
+                    }
+                }
+
                 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
 
                 // phone.dial() succeeded: we're now in a normal phone call.
@@ -425,11 +455,27 @@ public class PhoneUtils {
     }
 
     static void mergeCalls(Phone phone) {
-        try {
-            if (DBG) log("mergeCalls");
-            phone.conference();
-        } catch (CallStateException ex) {
-            Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
+        if (phone.getPhoneName().equals("GSM")) {
+            try {
+                if (DBG) log("mergeCalls");
+                phone.conference();
+            } catch (CallStateException ex) {
+                Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
+            }
+        } else { // CDMA
+            PhoneApp app = PhoneApp.getInstance();
+            if (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                // Send flash cmd
+                // TODO(Moto): Need to change the call from switchHoldingAndActive to
+                // something meaningful as we are not actually trying to swap calls but
+                // instead are merging two calls by sending a Flash command.
+                switchHoldingAndActive(phone);
+
+                // Set the Phone Call State to conference
+                app.cdmaPhoneCallState.setCurrentCallState(
+                        CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+            }
         }
     }
 
@@ -913,7 +959,14 @@ public class PhoneUtils {
      */
     static CallerInfoToken startGetCallerInfo(Context context, Call call,
             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
-        Connection conn = call.getEarliestConnection();
+        PhoneApp app = PhoneApp.getInstance();
+        Connection conn = null;
+        if (app.phone.getPhoneName().equals("CDMA")) {
+            conn = call.getLatestConnection();
+        } else {
+            conn = call.getEarliestConnection();
+        }
+
         return startGetCallerInfo(context, conn, listener, cookie);
     }
 
@@ -1071,12 +1124,12 @@ public class PhoneUtils {
         String compactName = null;
         if (info != null) {
             compactName = info.name;
-            if (compactName == null) {
+            if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
                 compactName = info.phoneNumber;
             }
         }
         // TODO: figure out UNKNOWN, PRIVATE numbers?
-        if (compactName == null) {
+        if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
             compactName = context.getString(R.string.unknown);
         }
         return compactName;
@@ -1112,12 +1165,12 @@ public class PhoneUtils {
         String compactName = null;
         if (ci != null) {
             compactName = ci.name;
-            if (compactName == null) {
+            if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
                 compactName = ci.phoneNumber;
             }
         }
         // TODO: figure out UNKNOWN, PRIVATE numbers?
-        if (compactName == null) {
+        if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
             compactName = context.getString(R.string.unknown);
         }
         return compactName;
@@ -1144,14 +1197,18 @@ public class PhoneUtils {
         // the UI or a "manage conference" function.  (Instead, when
         // you're in a 3-way call, all we can do is display the "generic"
         // state of the UI.)  So as far as the in-call UI is concerned,
-        // CDMA calls are *never* "conference calls".
-        if (PhoneApp.getInstance().phone.getPhoneName().equals("CDMA")) {
-            return false;
-        }
-
-        List<Connection> connections = call.getConnections();
-        if (connections != null && connections.size() > 1) {
-            return true;
+        // Conference corresponds to generic display.
+        PhoneApp app = PhoneApp.getInstance();
+        if (app.phone.getPhoneName().equals("CDMA")) {
+            if (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                return true;
+            }
+        } else {
+            List<Connection> connections = call.getConnections();
+            if (connections != null && connections.size() > 1) {
+                return true;
+            }
         }
         return false;
 
@@ -1428,25 +1485,52 @@ public class PhoneUtils {
         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
 
-        if (hasRingingCall) {
-            // If an incoming call is ringing, answer it (just like with the
-            // CALL button):
-            if (hasActiveCall && hasHoldingCall) {
-                if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
-                answerAndEndActive(phone);
+        if (phone.getPhoneName().equals("CDMA")) {
+            PhoneApp app = PhoneApp.getInstance();
+            if (hasRingingCall) {
+                answerCall(phone);
             } else {
-                if (DBG) log("handleHeadsetHook: ringing ==> answer!");
-                answerCall(phone);  // Automatically holds the current active call,
-                                     // if there is one
+                if (app.cdmaPhoneCallState.getCurrentCallState()
+                        == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
+                    // Send a flash command to CDMA network for putting the other
+                    // party on hold.
+                    // For CDMA networks which do not support this the user would just
+                    // hear a beep from the network.
+                    // For CDMA networks which do support it it will put the other
+                    // party on hold.
+                    switchHoldingAndActive(phone);
+                }
+
+                // No incoming ringing call.  Toggle the mute state.
+                if (getMute(phone)) {
+                    if (DBG) log("handleHeadsetHook: UNmuting...");
+                    setMute(phone, false);
+                } else {
+                    if (DBG) log("handleHeadsetHook: muting...");
+                    setMute(phone, true);
+                }
             }
-        } else {
-            // No incoming ringing call.  Toggle the mute state.
-            if (getMute(phone)) {
-                if (DBG) log("handleHeadsetHook: UNmuting...");
-                setMute(phone, false);
+        } else { // GSM
+            if (hasRingingCall) {
+                // If an incoming call is ringing, answer it (just like with the
+                // CALL button):
+                if (hasActiveCall && hasHoldingCall) {
+                    if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
+                    answerAndEndActive(phone);
+                } else {
+                    if (DBG) log("handleHeadsetHook: ringing ==> answer!");
+                    answerCall(phone);  // Automatically holds the current active call,
+                                     // if there is one
+                }
             } else {
-                if (DBG) log("handleHeadsetHook: muting...");
-                setMute(phone, true);
+                // No incoming ringing call.  Toggle the mute state.
+                if (getMute(phone)) {
+                    if (DBG) log("handleHeadsetHook: UNmuting...");
+                    setMute(phone, false);
+                } else {
+                    if (DBG) log("handleHeadsetHook: muting...");
+                    setMute(phone, true);
+                }
             }
         }
 
@@ -1502,15 +1586,22 @@ public class PhoneUtils {
      * state of the Phone.
      */
     /* package */ static boolean okToSwapCalls(Phone phone) {
-        // "Swap" is available if both lines are in use and there's no
-        // incoming call.  (Actually we need to verify that the active
-        // call really is in the ACTIVE state and the holding call really
-        // is in the HOLDING state, since you *can't* actually swap calls
-        // when the foreground call is DIALING or ALERTING.)
-        // TODO(CDMA): Need to handle the CDMA case too.
-        return phone.getRingingCall().isIdle()
-                && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
-                && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
+        if (phone.getPhoneName().equals("CDMA")) {
+            // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
+            // state by either accepting a Call Waiting or by merging two calls
+            PhoneApp app = PhoneApp.getInstance();
+            return (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+        } else {
+            // GSM: "Swap" is available if both lines are in use and there's no
+            // incoming call.  (Actually we need to verify that the active
+            // call really is in the ACTIVE state and the holding call really
+            // is in the HOLDING state, since you *can't* actually swap calls
+            // when the foreground call is DIALING or ALERTING.)
+            return phone.getRingingCall().isIdle()
+                    && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
+                    && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
+        }
     }
 
     /**
@@ -1518,11 +1609,17 @@ public class PhoneUtils {
      * state of the Phone.
      */
     /* package */ static boolean okToMergeCalls(Phone phone) {
-        // "Merge" is available if both lines are in use and there's no
-        // incoming call, *and* the current conference isn't already
-        // "full".
-        // TODO(CDMA): Need to handle the CDMA case too.
-        return phone.getRingingCall().isIdle() && phone.canConference();
+        if (phone.getPhoneName().equals("CDMA")) {
+            // CDMA: "Merge" is enabled only when the user is in a 3Way call.
+            PhoneApp app = PhoneApp.getInstance();
+            return (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
+        } else { //GSM.
+            // GSM: "Merge" is available if both lines are in use and there's no
+            // incoming call, *and* the current conference isn't already
+            // "full".
+            return phone.getRingingCall().isIdle() && phone.canConference();
+        }
     }
 
     /**
@@ -1530,28 +1627,34 @@ public class PhoneUtils {
      * state of the Phone.
      */
     /* package */ static boolean okToAddCall(Phone phone) {
-        // "Add call" is available only if ALL of the following are true:
-        // - There's no incoming ringing call
-        // - There's < 2 lines in use
-        // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
-        //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
-
-        // TODO(CDMA): Need to handle the CDMA case too.
-
-        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
-        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
-        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
-        final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
-        final Call.State fgCallState = phone.getForegroundCall().getState();
-
-        return !hasRingingCall
-                && !allLinesTaken
-                && ((fgCallState == Call.State.ACTIVE)
-                    || (fgCallState == Call.State.IDLE)
-                    || (fgCallState == Call.State.DISCONNECTED));
+       if (phone.getPhoneName().equals("CDMA")) {
+           // CDMA: "Add call" menu item is only enabled when the call is in
+           // - SINGLE_ACTIVE state
+           // - After 60 seconds of user Ignoring/Missing a Call Waiting call.
+            PhoneApp app = PhoneApp.getInstance();
+            return ((app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE)
+                    && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
+        } else {
+            // GSM: "Add call" is available only if ALL of the following are true:
+            // - There's no incoming ringing call
+            // - There's < 2 lines in use
+            // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
+            //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
+            final boolean hasRingingCall = !phone.getRingingCall().isIdle();
+            final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
+            final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
+            final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
+            final Call.State fgCallState = phone.getForegroundCall().getState();
+
+            return !hasRingingCall
+                    && !allLinesTaken
+                    && ((fgCallState == Call.State.ACTIVE)
+                        || (fgCallState == Call.State.IDLE)
+                        || (fgCallState == Call.State.DISCONNECTED));
+        }
     }
 
-
     //
     // General phone and call state debugging/testing code
     //