Merge "progress_large id defined incorrectly"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4f9dbfb..2721a8b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -538,7 +538,7 @@
         </receiver>
 
         <!-- BroadcastReceiver for receiving Intents from Notification mechanism. -->
-        <receiver android:name="PhoneGlobals$NotificationBroadcastReceiver" exported="false">
+        <receiver android:name="PhoneGlobals$NotificationBroadcastReceiver" android:exported="false">
             <intent-filter>
                 <action android:name="com.android.phone.ACTION_HANG_UP_ONGOING_CALL" />
                 <action android:name="com.android.phone.ACTION_CALL_BACK_FROM_NOTIFICATION" />
diff --git a/common/src/com/android/services/telephony/common/Call.java b/common/src/com/android/services/telephony/common/Call.java
index 4a74114..9bcf127 100644
--- a/common/src/com/android/services/telephony/common/Call.java
+++ b/common/src/com/android/services/telephony/common/Call.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.DisconnectCause;
 
 import com.android.internal.telephony.PhoneConstants;
 import com.google.android.collect.Sets;
@@ -93,56 +94,6 @@
                 | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
     }
 
-    /**
-     * Copy of states found in Connection object since Connection object is not available to the UI
-     * code.
-     * TODO: Consider cutting this down to only the types used by the UI.
-     * TODO: Consider adding a CUSTOM cause type and a customDisconnect member variable to
-     *       the Call object.  This would allow OEMs to extend the cause list without
-     *       needing to alter our implementation.
-     */
-    public enum DisconnectCause {
-        NOT_DISCONNECTED,               /* has not yet disconnected */
-        INCOMING_MISSED,                /* an incoming call that was missed and never answered */
-        NORMAL,                         /* normal; remote */
-        LOCAL,                          /* normal; local hangup */
-        BUSY,                           /* outgoing call to busy line */
-        CONGESTION,                     /* outgoing call to congested network */
-        MMI,                            /* not presently used; dial() returns null */
-        INVALID_NUMBER,                 /* invalid dial string */
-        NUMBER_UNREACHABLE,             /* cannot reach the peer */
-        SERVER_UNREACHABLE,             /* cannot reach the server */
-        INVALID_CREDENTIALS,            /* invalid credentials */
-        OUT_OF_NETWORK,                 /* calling from out of network is not allowed */
-        SERVER_ERROR,                   /* server error */
-        TIMED_OUT,                      /* client timed out */
-        LOST_SIGNAL,
-        LIMIT_EXCEEDED,                 /* eg GSM ACM limit exceeded */
-        INCOMING_REJECTED,              /* an incoming call that was rejected */
-        POWER_OFF,                      /* radio is turned off explicitly */
-        OUT_OF_SERVICE,                 /* out of service */
-        ICC_ERROR,                      /* No ICC, ICC locked, or other ICC error */
-        CALL_BARRED,                    /* call was blocked by call barring */
-        FDN_BLOCKED,                    /* call was blocked by fixed dial number */
-        CS_RESTRICTED,                  /* call was blocked by restricted all voice access */
-        CS_RESTRICTED_NORMAL,           /* call was blocked by restricted normal voice access */
-        CS_RESTRICTED_EMERGENCY,        /* call was blocked by restricted emergency voice access */
-        UNOBTAINABLE_NUMBER,            /* Unassigned number (3GPP TS 24.008 table 10.5.123) */
-        CDMA_LOCKED_UNTIL_POWER_CYCLE,  /* MS is locked until next power cycle */
-        CDMA_DROP,
-        CDMA_INTERCEPT,                 /* INTERCEPT order received, MS state idle entered */
-        CDMA_REORDER,                   /* MS has been redirected, call is cancelled */
-        CDMA_SO_REJECT,                 /* service option rejection */
-        CDMA_RETRY_ORDER,               /* requested service is rejected, retry delay is set */
-        CDMA_ACCESS_FAILURE,
-        CDMA_PREEMPTED,
-        CDMA_NOT_EMERGENCY,              /* not an emergency call */
-        CDMA_ACCESS_BLOCKED,            /* Access Blocked by CDMA network */
-        ERROR_UNSPECIFIED,
-
-        UNKNOWN                         /* Disconnect cause doesn't map to any above */
-    }
-
     private static final Map<Integer, String> STATE_MAP = ImmutableMap.<Integer, String>builder()
             .put(Call.State.ACTIVE, "ACTIVE")
             .put(Call.State.CALL_WAITING, "CALL_WAITING")
@@ -176,7 +127,8 @@
     private int mState = State.INVALID;
 
     // Reason for disconnect. Valid when the call state is DISCONNECTED.
-    private DisconnectCause mDisconnectCause = DisconnectCause.UNKNOWN;
+    // Valid values are defined in {@link DisconnectCause}.
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
 
     // Bit mask of capabilities unique to this call.
     private int mCapabilities;
@@ -258,7 +210,8 @@
         mIdentification.setCnapName(cnapName);
     }
 
-    public DisconnectCause getDisconnectCause() {
+    /** Returns call disconnect cause; values are defined in {@link DisconnectCause}. */
+    public int getDisconnectCause() {
         if (mState == State.DISCONNECTED || mState == State.IDLE) {
             return mDisconnectCause;
         }
@@ -266,7 +219,8 @@
         return DisconnectCause.NOT_DISCONNECTED;
     }
 
-    public void setDisconnectCause(DisconnectCause cause) {
+    /** Sets the call disconnect cause; values are defined in {@link DisconnectCause}. */
+    public void setDisconnectCause(int cause) {
         mDisconnectCause = cause;
     }
 
@@ -338,7 +292,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mCallId);
         dest.writeInt(mState);
-        dest.writeString(getDisconnectCause().toString());
+        dest.writeInt(getDisconnectCause());
         dest.writeInt(getCapabilities());
         dest.writeLong(getConnectTime());
         dest.writeIntArray(Ints.toArray(mChildCallIds));
@@ -353,7 +307,7 @@
     private Call(Parcel in) {
         mCallId = in.readInt();
         mState = in.readInt();
-        mDisconnectCause = DisconnectCause.valueOf(in.readString());
+        mDisconnectCause = in.readInt();
         mCapabilities = in.readInt();
         mConnectTime = in.readLong();
         mChildCallIds.addAll(Ints.asList(in.createIntArray()));
diff --git a/res/drawable-hdpi/ic_back_arrow.png b/res/drawable-hdpi/ic_back_arrow.png
new file mode 100644
index 0000000..aad4f36
--- /dev/null
+++ b/res/drawable-hdpi/ic_back_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_overflow_lt.png b/res/drawable-hdpi/ic_menu_overflow_lt.png
new file mode 100644
index 0000000..2561b8c
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_overflow_lt.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_back_arrow.png b/res/drawable-mdpi/ic_back_arrow.png
new file mode 100644
index 0000000..56eb887
--- /dev/null
+++ b/res/drawable-mdpi/ic_back_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_overflow_lt.png b/res/drawable-mdpi/ic_menu_overflow_lt.png
new file mode 100644
index 0000000..7dc68c9
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_overflow_lt.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_back_arrow.png b/res/drawable-xhdpi/ic_back_arrow.png
new file mode 100644
index 0000000..9d46e3d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_back_arrow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_overflow_lt.png b/res/drawable-xhdpi/ic_menu_overflow_lt.png
new file mode 100644
index 0000000..95e436c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_overflow_lt.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_back_arrow.png b/res/drawable-xxhdpi/ic_back_arrow.png
new file mode 100644
index 0000000..66b6e35
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_back_arrow.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_overflow_lt.png b/res/drawable-xxhdpi/ic_menu_overflow_lt.png
new file mode 100644
index 0000000..b9f0c3d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_overflow_lt.png
Binary files differ
diff --git a/res/drawable/actionbar_background.xml b/res/drawable/actionbar_background.xml
new file mode 100644
index 0000000..eabceac
--- /dev/null
+++ b/res/drawable/actionbar_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@color/actionbar_underline" />
+        </shape>
+    </item>
+    <item android:bottom="2dp">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/actionbar_background_color" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f0a3e9f..12b3a0b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -36,5 +36,12 @@
 
     <!-- Settings screen should use the same colors as the Dialer -->
     <color name="phone_settings_background_color">#f5f5f5</color>
-    <color name="phone_settings_actionbar_color">#e6e6e6</color>
+    <!-- Action bar text color.  Ensure this stays in sync with Dialer actionbar_text_color. -->
+    <color name="phone_settings_actionbar_text_color">#FFFFFF</color>
+    <!-- Background color of action bars.  Ensure this stays in sync with Dialer
+         actionbar_background_color. -->
+    <color name="actionbar_background_color">#3B77E7</color>
+    <!-- Underline color of action bars.  Ensure this stays in sync with Dialer
+         actionbar_underline. -->
+    <color name="actionbar_underline">#3265C1</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8e92006..47d066b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -379,6 +379,10 @@
     <string name="roaming_reenable_message">You\'ve lost data connectivity because you left your home network with data roaming turned off.</string>
     <!-- Mobile network settings screen, dialog message when user selects the Data roaming check box -->
     <string name="roaming_warning">Allow data roaming? You may incur significant roaming charges!</string>
+
+    <!-- USSD aggregation dialog box: separator strings between messages (new-lines will be added before and after) -->
+    <string name="ussd_dialog_sep" translatable="false">----------</string>
+
     <string name="gsm_umts_options">GSM/UMTS Options</string>
     <string name="cdma_options">CDMA Options</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 59edb80..c3ad5b8 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -163,6 +163,8 @@
     <style name="SettingsLight" parent="@android:style/Theme.Holo.Light">
         <item name="android:windowBackground">@color/phone_settings_background_color</item>
         <item name="android:actionBarStyle">@style/DialtactsActionBarStyle</item>
+        <item name="android:actionOverflowButtonStyle">@style/DialtactsActionBarOverflow</item>
+        <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
     </style>
 
     <style name="Empty" parent="@android:style/Theme.Holo">
@@ -225,13 +227,27 @@
         <item name="android:backgroundStacked">@color/people_app_theme_color</item>
     </style>
 
-    <style name="DialtactsActionBarStyle"
-           parent="@android:style/Widget.Holo.Light.ActionBar">
-        <item name="android:background">@color/phone_settings_actionbar_color</item>
+    <!-- Style for the call settings action bar.  Should be kept in sync with Dialer. -->
+    <style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
+        <item name="android:background">@drawable/actionbar_background</item>
+        <item name="android:backgroundStacked">#ffffff</item>
+        <item name="android:titleTextStyle">@style/DialtactsActionBarTitleText</item>
         <!-- Empty icon -->
         <item name="android:icon">@android:color/transparent</item>
     </style>
 
+    <!-- Text in the action bar at the top of the screen.  Should be kept in sync with Dialer. -->
+    <style name="DialtactsActionBarTitleText"
+           parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
+        <item name="android:textColor">@color/phone_settings_actionbar_text_color</item>
+    </style>
+
+    <!-- Action bar overflow menu icon. -->
+    <style name="DialtactsActionBarOverflow"
+           parent="@android:style/Widget.Holo.ActionButton.Overflow">
+        <item name="android:src">@drawable/ic_menu_overflow_lt</item>
+    </style>
+
     <style name="SimImportTheme"
            parent="@android:style/Theme.Holo.Light.DarkActionBar">
         <item name="android:actionBarStyle">@style/ContactsActionBarStyle</item>
diff --git a/src/com/android/phone/BluetoothPhoneService.java b/src/com/android/phone/BluetoothPhoneService.java
index cd9c696..a2e1288 100644
--- a/src/com/android/phone/BluetoothPhoneService.java
+++ b/src/com/android/phone/BluetoothPhoneService.java
@@ -42,6 +42,8 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.CallManager;
 
+import com.android.phone.CallGatewayManager.RawGatewayInfo;
+
 import java.io.IOException;
 import java.util.LinkedList;
 import java.util.List;
@@ -60,6 +62,7 @@
 
     private BluetoothAdapter mAdapter;
     private CallManager mCM;
+    private CallGatewayManager mCallGatewayManager;
 
     private BluetoothHeadset mBluetoothHeadset;
 
@@ -104,6 +107,7 @@
             if (VDBG) Log.d(TAG, "mAdapter null");
             return;
         }
+        mCallGatewayManager = CallGatewayManager.getInstance();
 
         mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
         mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
@@ -332,7 +336,9 @@
         }
         // end the result
         // when index is 0, other parameter does not matter
-        mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
+        }
     }
 
     private void handleQueryPhoneState() {
@@ -519,15 +525,27 @@
             mpty = call.isMultiparty();
         }
 
-        int direction = connection.isIncoming() ? 1 : 0;
+        boolean isIncoming = connection.isIncoming();
 
+        // For GV outgoing calls send the contact phone #, not the gateway #.
         String number = connection.getAddress();
+        if (!isIncoming) {
+            RawGatewayInfo rawInfo = mCallGatewayManager.getGatewayInfo(connection);
+            if (!rawInfo.isEmpty()) {
+                number = rawInfo.trueNumber;
+            }
+        }
         int type = -1;
         if (number != null) {
             type = PhoneNumberUtils.toaFromString(number);
+        } else {
+            number = "";
         }
 
-        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(index + 1, isIncoming ? 1 : 0,
+                    state, 0, mpty, number, type);
+        }
     }
 
     /** Build the +CLCC result for CDMA
@@ -650,9 +668,16 @@
                 // as per Bluetooth SIG PTS
         }
 
-        int direction = connection.isIncoming() ? 1 : 0;
+        boolean isIncoming = connection.isIncoming();
 
+        // For GV outgoing calls send the contact phone #, not the gateway #.
         String number = connection.getAddress();
+        if (!isIncoming) {
+            RawGatewayInfo rawInfo = mCallGatewayManager.getGatewayInfo(connection);
+            if (!rawInfo.isEmpty()) {
+                number = rawInfo.trueNumber;
+            }
+        }
         int type = -1;
         if (number != null) {
             type = PhoneNumberUtils.toaFromString(number);
@@ -660,7 +685,10 @@
             number = "";
         }
 
-        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(index + 1, isIncoming ? 1 : 0,
+                    state, 0, mpty, number, type);
+        }
     }
 
     private void handleCdmaSwapSecondCallState() {
@@ -730,6 +758,10 @@
                     }
                     return true;
                 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                    if (ringingCall.isRinging() && (mNumHeld > 0 && mNumActive == 0)) {
+                       if (VDBG) log("CHLD:1 Answer the Call");
+                       return PhoneUtils.answerCall(ringingCall);
+                    }
                     // Hangup active call, answer held call
                     return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
                 } else {
@@ -762,7 +794,11 @@
                     Log.e(TAG, "CDMA fail to do hold active and accept held");
                     return false;
                 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-                    PhoneUtils.switchHoldingAndActive(backgroundCall);
+                    if (ringingCall.isRinging() && (mNumHeld > 0 && mNumActive == 0)) {
+                       PhoneUtils.answerCall(ringingCall);
+                    } else {
+                       PhoneUtils.switchHoldingAndActive(backgroundCall);
+                    }
                     return true;
                 } else {
                     Log.e(TAG, "Unexpected phone type: " + phoneType);
diff --git a/src/com/android/phone/CallCommandService.java b/src/com/android/phone/CallCommandService.java
index 5238911..2076979 100644
--- a/src/com/android/phone/CallCommandService.java
+++ b/src/com/android/phone/CallCommandService.java
@@ -168,30 +168,10 @@
 
     @Override
     public void swap() {
-        if (!PhoneUtils.okToSwapCalls(mCallManager)) {
-            // TODO: throw an error instead?
-            return;
-        }
-
-        // Swap the fg and bg calls.
-        // In the future we may provides some way for user to choose among
-        // multiple background calls, for now, always act on the first background calll.
-        PhoneUtils.switchHoldingAndActive(mCallManager.getFirstActiveBgCall());
-
-        final PhoneGlobals mApp = PhoneGlobals.getInstance();
-
-        // If we have a valid BluetoothPhoneService then since CDMA network or
-        // Telephony FW does not send us information on which caller got swapped
-        // we need to update the second call active state in BluetoothPhoneService internally
-        if (mCallManager.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
-            final IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
-            if (btPhone != null) {
-                try {
-                    btPhone.cdmaSwapSecondCallState();
-                } catch (RemoteException e) {
-                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                }
-            }
+        try {
+            PhoneUtils.swap();
+        } catch (Exception e) {
+            Log.e(TAG, "Error during swap().", e);
         }
     }
 
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 5cc3aa3..035baaf 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -46,6 +46,7 @@
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceManager;
 import android.preference.PreferenceScreen;
@@ -174,6 +175,8 @@
     private static final String BUTTON_FDN_KEY   = "button_fdn_key";
     private static final String BUTTON_RESPOND_VIA_SMS_KEY   = "button_respond_via_sms_key";
 
+    private static final String BUTTON_RINGTONE_CATEGORY = "button_ringtone_category_key";
+
     private static final String BUTTON_RINGTONE_KEY    = "button_ringtone_key";
     private static final String BUTTON_VIBRATE_ON_RING = "button_vibrate_on_ring";
     private static final String BUTTON_PLAY_DTMF_TONE  = "button_play_dtmf_tone";
@@ -1539,7 +1542,8 @@
             if (vibrator != null && vibrator.hasVibrator()) {
                 mVibrateWhenRinging.setOnPreferenceChangeListener(this);
             } else {
-                prefSet.removePreference(mVibrateWhenRinging);
+                PreferenceCategory ringToneCategory = (PreferenceCategory)findPreference(BUTTON_RINGTONE_CATEGORY);
+                ringToneCategory.removePreference(mVibrateWhenRinging);
                 mVibrateWhenRinging = null;
             }
         }
diff --git a/src/com/android/phone/CallGatewayManager.java b/src/com/android/phone/CallGatewayManager.java
index 6fe2444..81bee07 100644
--- a/src/com/android/phone/CallGatewayManager.java
+++ b/src/com/android/phone/CallGatewayManager.java
@@ -25,7 +25,7 @@
 import com.android.internal.telephony.Connection;
 import com.google.android.collect.Maps;
 
-import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This class manages gateway information for outgoing calls. When calls are made, they may contain
@@ -68,9 +68,19 @@
 
     public static final RawGatewayInfo EMPTY_INFO = new RawGatewayInfo(null, null, null);
 
-    private final HashMap<Connection, RawGatewayInfo> mMap = Maps.newHashMap();
+    private final ConcurrentHashMap<Connection, RawGatewayInfo> mMap =
+        new ConcurrentHashMap<Connection, RawGatewayInfo>(4, 0.9f, 1);
 
-    public CallGatewayManager() {
+    private static CallGatewayManager sSingleton;
+
+    public static synchronized CallGatewayManager getInstance() {
+        if (sSingleton == null) {
+            sSingleton = new CallGatewayManager();
+        }
+        return sSingleton;
+    }
+
+    private CallGatewayManager() {
     }
 
     /**
diff --git a/src/com/android/phone/CallLogger.java b/src/com/android/phone/CallLogger.java
index 644812f..9e76db4 100644
--- a/src/com/android/phone/CallLogger.java
+++ b/src/com/android/phone/CallLogger.java
@@ -26,6 +26,7 @@
 import android.net.Uri;
 import android.os.SystemProperties;
 import android.provider.CallLog.Calls;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Log;
@@ -87,13 +88,13 @@
      * Came as logCall(Connection,int) but calculates the call type from the connection object.
      */
     public void logCall(Connection c) {
-        final Connection.DisconnectCause cause = c.getDisconnectCause();
+        final int cause = c.getDisconnectCause();
 
         // Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
         final int callLogType;
 
         if (c.isIncoming()) {
-            callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
+            callLogType = (cause == DisconnectCause.INCOMING_MISSED ?
                            Calls.MISSED_TYPE : Calls.INCOMING_TYPE);
         } else {
             callLogType = Calls.OUTGOING_TYPE;
diff --git a/src/com/android/phone/CallModeler.java b/src/com/android/phone/CallModeler.java
index e4ed147..72eaed0 100644
--- a/src/com/android/phone/CallModeler.java
+++ b/src/com/android/phone/CallModeler.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Log;
@@ -38,7 +39,6 @@
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Lists;
 
@@ -555,8 +555,7 @@
             changed = true;
         }
 
-        final Call.DisconnectCause newDisconnectCause =
-                translateDisconnectCauseFromTelephony(connection.getDisconnectCause());
+        final int newDisconnectCause = connection.getDisconnectCause();
         if (call.getDisconnectCause() != newDisconnectCause) {
             call.setDisconnectCause(newDisconnectCause);
             changed = true;
@@ -789,73 +788,6 @@
         return retval;
     }
 
-    private final ImmutableMap<Connection.DisconnectCause, Call.DisconnectCause> CAUSE_MAP =
-            ImmutableMap.<Connection.DisconnectCause, Call.DisconnectCause>builder()
-                .put(Connection.DisconnectCause.BUSY, Call.DisconnectCause.BUSY)
-                .put(Connection.DisconnectCause.CALL_BARRED, Call.DisconnectCause.CALL_BARRED)
-                .put(Connection.DisconnectCause.CDMA_ACCESS_BLOCKED,
-                        Call.DisconnectCause.CDMA_ACCESS_BLOCKED)
-                .put(Connection.DisconnectCause.CDMA_ACCESS_FAILURE,
-                        Call.DisconnectCause.CDMA_ACCESS_FAILURE)
-                .put(Connection.DisconnectCause.CDMA_DROP, Call.DisconnectCause.CDMA_DROP)
-                .put(Connection.DisconnectCause.CDMA_INTERCEPT, Call.DisconnectCause.CDMA_INTERCEPT)
-                .put(Connection.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
-                        Call.DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE)
-                .put(Connection.DisconnectCause.CDMA_NOT_EMERGENCY,
-                        Call.DisconnectCause.CDMA_NOT_EMERGENCY)
-                .put(Connection.DisconnectCause.CDMA_PREEMPTED, Call.DisconnectCause.CDMA_PREEMPTED)
-                .put(Connection.DisconnectCause.CDMA_REORDER, Call.DisconnectCause.CDMA_REORDER)
-                .put(Connection.DisconnectCause.CDMA_RETRY_ORDER,
-                        Call.DisconnectCause.CDMA_RETRY_ORDER)
-                .put(Connection.DisconnectCause.CDMA_SO_REJECT, Call.DisconnectCause.CDMA_SO_REJECT)
-                .put(Connection.DisconnectCause.CONGESTION, Call.DisconnectCause.CONGESTION)
-                .put(Connection.DisconnectCause.CS_RESTRICTED, Call.DisconnectCause.CS_RESTRICTED)
-                .put(Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY,
-                        Call.DisconnectCause.CS_RESTRICTED_EMERGENCY)
-                .put(Connection.DisconnectCause.CS_RESTRICTED_NORMAL,
-                        Call.DisconnectCause.CS_RESTRICTED_NORMAL)
-                .put(Connection.DisconnectCause.ERROR_UNSPECIFIED,
-                        Call.DisconnectCause.ERROR_UNSPECIFIED)
-                .put(Connection.DisconnectCause.FDN_BLOCKED, Call.DisconnectCause.FDN_BLOCKED)
-                .put(Connection.DisconnectCause.ICC_ERROR, Call.DisconnectCause.ICC_ERROR)
-                .put(Connection.DisconnectCause.INCOMING_MISSED,
-                        Call.DisconnectCause.INCOMING_MISSED)
-                .put(Connection.DisconnectCause.INCOMING_REJECTED,
-                        Call.DisconnectCause.INCOMING_REJECTED)
-                .put(Connection.DisconnectCause.INVALID_CREDENTIALS,
-                        Call.DisconnectCause.INVALID_CREDENTIALS)
-                .put(Connection.DisconnectCause.INVALID_NUMBER,
-                        Call.DisconnectCause.INVALID_NUMBER)
-                .put(Connection.DisconnectCause.LIMIT_EXCEEDED, Call.DisconnectCause.LIMIT_EXCEEDED)
-                .put(Connection.DisconnectCause.LOCAL, Call.DisconnectCause.LOCAL)
-                .put(Connection.DisconnectCause.LOST_SIGNAL, Call.DisconnectCause.LOST_SIGNAL)
-                .put(Connection.DisconnectCause.MMI, Call.DisconnectCause.MMI)
-                .put(Connection.DisconnectCause.NORMAL, Call.DisconnectCause.NORMAL)
-                .put(Connection.DisconnectCause.NOT_DISCONNECTED,
-                        Call.DisconnectCause.NOT_DISCONNECTED)
-                .put(Connection.DisconnectCause.NUMBER_UNREACHABLE,
-                        Call.DisconnectCause.NUMBER_UNREACHABLE)
-                .put(Connection.DisconnectCause.OUT_OF_NETWORK, Call.DisconnectCause.OUT_OF_NETWORK)
-                .put(Connection.DisconnectCause.OUT_OF_SERVICE, Call.DisconnectCause.OUT_OF_SERVICE)
-                .put(Connection.DisconnectCause.POWER_OFF, Call.DisconnectCause.POWER_OFF)
-                .put(Connection.DisconnectCause.SERVER_ERROR, Call.DisconnectCause.SERVER_ERROR)
-                .put(Connection.DisconnectCause.SERVER_UNREACHABLE,
-                        Call.DisconnectCause.SERVER_UNREACHABLE)
-                .put(Connection.DisconnectCause.TIMED_OUT, Call.DisconnectCause.TIMED_OUT)
-                .put(Connection.DisconnectCause.UNOBTAINABLE_NUMBER,
-                        Call.DisconnectCause.UNOBTAINABLE_NUMBER)
-                .build();
-
-    private Call.DisconnectCause translateDisconnectCauseFromTelephony(
-            Connection.DisconnectCause causeSource) {
-
-        if (CAUSE_MAP.containsKey(causeSource)) {
-            return CAUSE_MAP.get(causeSource);
-        }
-
-        return Call.DisconnectCause.UNKNOWN;
-    }
-
     /**
      * Gets an existing callId for a connection, or creates one if none exists.
      * This function does NOT set any of the Connection data onto the Call class.
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 80807a2..aa4270e 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -46,6 +46,7 @@
 import android.os.Vibrator;
 import android.provider.CallLog.Calls;
 import android.provider.Settings;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
@@ -899,7 +900,7 @@
         mVoicePrivacyState = false;
         Connection c = (Connection) r.result;
         if (c != null) {
-            log("onDisconnect: cause = " + c.getDisconnectCause()
+            log("onDisconnect: cause = " + DisconnectCause.toString(c.getDisconnectCause())
                   + ", incoming = " + c.isIncoming()
                   + ", date = " + c.getCreateTime());
         } else {
@@ -974,34 +975,34 @@
 
         // The "Busy" or "Congestion" tone is the highest priority:
         if (c != null) {
-            Connection.DisconnectCause cause = c.getDisconnectCause();
-            if (cause == Connection.DisconnectCause.BUSY) {
+            int cause = c.getDisconnectCause();
+            if (cause == DisconnectCause.BUSY) {
                 if (DBG) log("- need to play BUSY tone!");
                 toneToPlay = InCallTonePlayer.TONE_BUSY;
-            } else if (cause == Connection.DisconnectCause.CONGESTION) {
+            } else if (cause == DisconnectCause.CONGESTION) {
                 if (DBG) log("- need to play CONGESTION tone!");
                 toneToPlay = InCallTonePlayer.TONE_CONGESTION;
-            } else if (((cause == Connection.DisconnectCause.NORMAL)
-                    || (cause == Connection.DisconnectCause.LOCAL))
+            } else if (((cause == DisconnectCause.NORMAL)
+                    || (cause == DisconnectCause.LOCAL))
                     && (mApplication.isOtaCallInActiveState())) {
                 if (DBG) log("- need to play OTA_CALL_END tone!");
                 toneToPlay = InCallTonePlayer.TONE_OTA_CALL_END;
-            } else if (cause == Connection.DisconnectCause.CDMA_REORDER) {
+            } else if (cause == DisconnectCause.CDMA_REORDER) {
                 if (DBG) log("- need to play CDMA_REORDER tone!");
                 toneToPlay = InCallTonePlayer.TONE_REORDER;
-            } else if (cause == Connection.DisconnectCause.CDMA_INTERCEPT) {
+            } else if (cause == DisconnectCause.CDMA_INTERCEPT) {
                 if (DBG) log("- need to play CDMA_INTERCEPT tone!");
                 toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
-            } else if (cause == Connection.DisconnectCause.CDMA_DROP) {
+            } else if (cause == DisconnectCause.CDMA_DROP) {
                 if (DBG) log("- need to play CDMA_DROP tone!");
                 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
-            } else if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
+            } else if (cause == DisconnectCause.OUT_OF_SERVICE) {
                 if (DBG) log("- need to play OUT OF SERVICE tone!");
                 toneToPlay = InCallTonePlayer.TONE_OUT_OF_SERVICE;
-            } else if (cause == Connection.DisconnectCause.UNOBTAINABLE_NUMBER) {
+            } else if (cause == DisconnectCause.UNOBTAINABLE_NUMBER) {
                 if (DBG) log("- need to play TONE_UNOBTAINABLE_NUMBER tone!");
                 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
-            } else if (cause == Connection.DisconnectCause.ERROR_UNSPECIFIED) {
+            } else if (cause == DisconnectCause.ERROR_UNSPECIFIED) {
                 if (DBG) log("- DisconnectCause is ERROR_UNSPECIFIED: play TONE_CALL_ENDED!");
                 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
             }
@@ -1017,9 +1018,9 @@
         if ((toneToPlay == InCallTonePlayer.TONE_NONE)
             && (mCM.getState() == PhoneConstants.State.IDLE)
             && (c != null)) {
-            Connection.DisconnectCause cause = c.getDisconnectCause();
-            if ((cause == Connection.DisconnectCause.NORMAL)  // remote hangup
-                || (cause == Connection.DisconnectCause.LOCAL)) {  // local hangup
+            int cause = c.getDisconnectCause();
+            if ((cause == DisconnectCause.NORMAL)  // remote hangup
+                || (cause == DisconnectCause.LOCAL)) {  // local hangup
                 if (VDBG) log("- need to play CALL_ENDED tone!");
                 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
                 mIsCdmaRedialCall = false;
@@ -1055,9 +1056,9 @@
             }
 
             final long date = c.getCreateTime();
-            final Connection.DisconnectCause cause = c.getDisconnectCause();
+            final int cause = c.getDisconnectCause();
             final boolean missedCall = c.isIncoming() &&
-                    (cause == Connection.DisconnectCause.INCOMING_MISSED);
+                    (cause == DisconnectCause.INCOMING_MISSED);
             if (missedCall) {
                 // Show the "Missed call" notification.
                 // (Note we *don't* do this if this was an incoming call that
@@ -1086,10 +1087,10 @@
             if (((mPreviousCdmaCallState == Call.State.DIALING)
                     || (mPreviousCdmaCallState == Call.State.ALERTING))
                     && (!isEmergencyNumber)
-                    && (cause != Connection.DisconnectCause.INCOMING_MISSED )
-                    && (cause != Connection.DisconnectCause.NORMAL)
-                    && (cause != Connection.DisconnectCause.LOCAL)
-                    && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
+                    && (cause != DisconnectCause.INCOMING_MISSED )
+                    && (cause != DisconnectCause.NORMAL)
+                    && (cause != DisconnectCause.LOCAL)
+                    && (cause != DisconnectCause.INCOMING_REJECTED)) {
                 if (!mIsCdmaRedialCall) {
                     if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
                         // TODO: (Moto): The contact reference data may need to be stored and use
diff --git a/src/com/android/phone/EmergencyCallHelper.java b/src/com/android/phone/EmergencyCallHelper.java
index 47f0e54..74ce088 100644
--- a/src/com/android/phone/EmergencyCallHelper.java
+++ b/src/com/android/phone/EmergencyCallHelper.java
@@ -29,6 +29,7 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.DisconnectCause;
 import android.telephony.ServiceState;
 import android.util.Log;
 
@@ -234,11 +235,12 @@
      */
     private void onDisconnect(Message msg) {
         Connection conn = (Connection) ((AsyncResult) msg.obj).result;
-        Connection.DisconnectCause cause = conn.getDisconnectCause();
+        int cause = conn.getDisconnectCause();
         if (DBG) log("onDisconnect: connection '" + conn
-                     + "', addr '" + conn.getAddress() + "', cause = " + cause);
+                     + "', addr '" + conn.getAddress()
+                     + "', cause = " + DisconnectCause.toString(cause));
 
-        if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
+        if (cause == DisconnectCause.OUT_OF_SERVICE) {
             // Wait a bit more and try again (or just bail out totally if
             // we've had too many failures.)
             if (DBG) log("- onDisconnect: OUT_OF_SERVICE, need to retry...");
diff --git a/src/com/android/phone/OutgoingCallBroadcaster.java b/src/com/android/phone/OutgoingCallBroadcaster.java
index 6e0c13e..1267286 100644
--- a/src/com/android/phone/OutgoingCallBroadcaster.java
+++ b/src/com/android/phone/OutgoingCallBroadcaster.java
@@ -46,30 +46,35 @@
 import com.android.internal.telephony.TelephonyCapabilities;
 
 /**
- * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and
- * broadcasts the ACTION_NEW_OUTGOING_CALL intent which allows other
- * applications to monitor, redirect, or prevent the outgoing call.
-
+ * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and broadcasts the
+ * ACTION_NEW_OUTGOING_CALL intent. ACTION_NEW_OUTGOING_CALL is an ordered broadcast intent which
+ * contains the phone number being dialed. Applications can use this intent to (1) see which numbers
+ * are being dialed, (2) redirect a call (change the number being dialed), or (3) prevent a call
+ * from being placed.
+ *
  * After the other applications have had a chance to see the
  * ACTION_NEW_OUTGOING_CALL intent, it finally reaches the
  * {@link OutgoingCallReceiver}, which passes the (possibly modified)
  * intent on to the {@link SipCallOptionHandler}, which will
  * ultimately start the call using the CallController.placeCall() API.
  *
- * Emergency calls and calls where no number is present (like for a CDMA
- * "empty flash" or a nonexistent voicemail number) are exempt from being
- * broadcast.
+ * Calls where no number is present (like for a CDMA "empty flash" or a nonexistent voicemail
+ * number) are exempt from being broadcast.
+ * Calls to emergency numbers are still broadcast for informative purposes. The call is placed
+ * prior to sending ACTION_NEW_OUTGOING_CALL and cannot be redirected nor prevented.
  */
 public class OutgoingCallBroadcaster extends Activity
         implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
 
-    private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;
     private static final String TAG = "OutgoingCallBroadcaster";
     private static final boolean DBG =
             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     // Do not check in with VDBG = true, since that may write PII to the system log.
     private static final boolean VDBG = false;
 
+    /** Required permission for any app that wants to consume ACTION_NEW_OUTGOING_CALL. */
+    private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;
+
     public static final String ACTION_SIP_SELECT_PHONE = "com.android.phone.SIP_SELECT_PHONE";
     public static final String EXTRA_ALREADY_CALLED = "android.phone.extra.ALREADY_CALLED";
     public static final String EXTRA_ORIGINAL_URI = "android.phone.extra.ORIGINAL_URI";
@@ -459,7 +464,7 @@
             launchedFromUid = -1;
             launchedFromPackage = null;
         }
-        if (appOps.noteOp(AppOpsManager.OP_CALL_PHONE, launchedFromUid, launchedFromPackage)
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_CALL_PHONE, launchedFromUid, launchedFromPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             Log.w(TAG, "Rejecting call from uid " + launchedFromUid + " package "
                     + launchedFromPackage);
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 3f35900..62d542f 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -447,7 +447,7 @@
 
             CallLogger callLogger = new CallLogger(this, new CallLogAsync());
 
-            callGatewayManager = new CallGatewayManager();
+            callGatewayManager = CallGatewayManager.getInstance();
 
             // Create the CallController singleton, which is the interface
             // to the telephony layer for user-initiated telephony functionality
@@ -489,7 +489,8 @@
             callHandlerServiceProxy = new CallHandlerServiceProxy(this, callModeler,
                     callCommandService, audioRouter);
 
-            phoneMgr = PhoneInterfaceManager.init(this, phone, callHandlerServiceProxy);
+            phoneMgr = PhoneInterfaceManager.init(this, phone, callHandlerServiceProxy, callModeler,
+                    dtmfTonePlayer);
 
             // Create the CallNotifer singleton, which handles
             // asynchronous events from the telephony layer (like
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 82b5e9f..cf86e7a 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.bluetooth.IBluetoothHeadsetPhone;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -27,9 +28,11 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.telephony.NeighboringCellInfo;
@@ -38,21 +41,29 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.DefaultPhoneNotifier;
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.ITelephonyListener;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.CallManager;
-import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.services.telephony.common.Call;
 
-import java.util.List;
+import com.android.internal.util.HexDump;
+
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Implementation of the ITelephony interface.
  */
-public class PhoneInterfaceManager extends ITelephony.Stub {
+public class PhoneInterfaceManager extends ITelephony.Stub implements CallModeler.Listener {
     private static final String LOG_TAG = "PhoneInterfaceManager";
     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
     private static final boolean DBG_LOC = false;
@@ -74,6 +85,14 @@
     AppOpsManager mAppOps;
     MainThreadHandler mMainThreadHandler;
     CallHandlerServiceProxy mCallHandlerService;
+    CallModeler mCallModeler;
+    DTMFTonePlayer mDtmfTonePlayer;
+    Handler mDtmfStopHandler = new Handler();
+    Runnable mDtmfStopRunnable;
+
+    private final List<ITelephonyListener> mListeners = new ArrayList<ITelephonyListener>();
+    private final Map<IBinder, TelephonyListenerDeathRecipient> mDeathRecipients =
+            new HashMap<IBinder, TelephonyListenerDeathRecipient>();
 
     /**
      * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
@@ -221,10 +240,12 @@
      * This is only done once, at startup, from PhoneApp.onCreate().
      */
     /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone,
-            CallHandlerServiceProxy callHandlerService) {
+                CallHandlerServiceProxy callHandlerService, CallModeler callModeler,
+                DTMFTonePlayer dtmfTonePlayer) {
         synchronized (PhoneInterfaceManager.class) {
             if (sInstance == null) {
-                sInstance = new PhoneInterfaceManager(app, phone, callHandlerService);
+                sInstance = new PhoneInterfaceManager(app, phone, callHandlerService, callModeler,
+                        dtmfTonePlayer);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -234,13 +255,17 @@
 
     /** Private constructor; @see init() */
     private PhoneInterfaceManager(PhoneGlobals app, Phone phone,
-            CallHandlerServiceProxy callHandlerService) {
+            CallHandlerServiceProxy callHandlerService, CallModeler callModeler,
+            DTMFTonePlayer dtmfTonePlayer) {
         mApp = app;
         mPhone = phone;
         mCM = PhoneGlobals.getInstance().mCM;
         mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
         mMainThreadHandler = new MainThreadHandler();
         mCallHandlerService = callHandlerService;
+        mCallModeler = callModeler;
+        mCallModeler.addListener(this);
+        mDtmfTonePlayer = dtmfTonePlayer;
         publish();
     }
 
@@ -793,6 +818,15 @@
         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
     }
 
+    /**
+     * Make sure the caller has the READ_PRIVILEGED_PHONE_STATE permission.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforcePrivilegedPhoneStatePermission() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                null);
+    }
 
     private String createTelUrl(String number) {
         if (TextUtils.isEmpty(number)) {
@@ -897,4 +931,248 @@
     public int getLteOnCdmaMode() {
         return mPhone.getLteOnCdmaMode();
     }
+
+    @Override
+    public void toggleHold() {
+        enforceModifyPermission();
+
+        try {
+            PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error during toggleHold().", e);
+        }
+    }
+
+    @Override
+    public void merge() {
+        enforceModifyPermission();
+
+        try {
+            if (PhoneUtils.okToMergeCalls(mCM)) {
+                PhoneUtils.mergeCalls(mCM);
+            }
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error during merge().", e);
+        }
+    }
+
+    @Override
+    public void swap() {
+        enforceModifyPermission();
+
+        try {
+            PhoneUtils.swap();
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error during swap().", e);
+        }
+    }
+
+    @Override
+    public void mute(boolean onOff) {
+        enforceModifyPermission();
+
+        try {
+            PhoneUtils.setMute(onOff);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error during mute().", e);
+        }
+    }
+
+    @Override
+    public void playDtmfTone(char digit, boolean timedShortTone) {
+        enforceModifyPermission();
+
+        synchronized (mDtmfStopHandler) {
+            try {
+                mDtmfTonePlayer.playDtmfTone(digit, timedShortTone);
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error playing DTMF tone.", e);
+            }
+
+            if (mDtmfStopRunnable != null) {
+                mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
+            }
+            mDtmfStopRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (mDtmfStopHandler) {
+                        if (mDtmfStopRunnable == this) {
+                            mDtmfTonePlayer.stopDtmfTone();
+                            mDtmfStopRunnable = null;
+                        }
+                    }
+                }
+            };
+            mDtmfStopHandler.postDelayed(mDtmfStopRunnable, 5000);
+        }
+    }
+
+    @Override
+    public void stopDtmfTone() {
+        enforceModifyPermission();
+
+        synchronized (mDtmfStopHandler) {
+            try {
+                mDtmfTonePlayer.stopDtmfTone();
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error stopping DTMF tone.", e);
+            }
+
+            if (mDtmfStopRunnable != null) {
+                mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
+                mDtmfStopRunnable = null;
+            }
+        }
+    }
+
+    @Override
+    public void addListener(ITelephonyListener listener) {
+        enforcePrivilegedPhoneStatePermission();
+
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null.");
+        }
+
+        synchronized (mListeners) {
+            IBinder listenerBinder = listener.asBinder();
+            for (ITelephonyListener l : mListeners) {
+                if (l.asBinder().equals(listenerBinder)) {
+                    Log.w(LOG_TAG, "Listener already registered. Ignoring.");
+                    return;
+                }
+            }
+            mListeners.add(listener);
+            mDeathRecipients.put(listener.asBinder(),
+                    new TelephonyListenerDeathRecipient(listener.asBinder()));
+
+            // update the new listener so they get the full call state immediately
+            for (Call call : mCallModeler.getFullList()) {
+                try {
+                    notifyListenerOfCallLocked(call, listener);
+                } catch (RemoteException e) {
+                    Log.e(LOG_TAG, "Error updating new listener. Ignoring.");
+                    removeListenerInternal(listener);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeListener(ITelephonyListener listener) {
+        enforcePrivilegedPhoneStatePermission();
+
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener must not be null.");
+        }
+
+        removeListenerInternal(listener);
+    }
+
+    private void removeListenerInternal(ITelephonyListener listener) {
+        IBinder listenerBinder = listener.asBinder();
+
+        synchronized (mListeners) {
+            for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
+                ITelephonyListener nextListener = it.next();
+                if (nextListener.asBinder().equals(listenerBinder)) {
+                    TelephonyListenerDeathRecipient dr = mDeathRecipients.get(listener.asBinder());
+                    if (dr != null) {
+                        dr.unlinkDeathRecipient();
+                    }
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    /** CallModeler.Listener implementation **/
+
+    @Override
+    public void onDisconnect(Call call) {
+        notifyListenersOfCall(call);
+    }
+
+    @Override
+    public void onIncoming(Call call) {
+        notifyListenersOfCall(call);
+    }
+
+    @Override
+    public void onUpdate(List<Call> calls) {
+        for (Call call : calls) {
+            notifyListenersOfCall(call);
+        }
+    }
+
+    @Override
+    public void onPostDialAction(
+            Connection.PostDialState state, int callId, String remainingChars, char c) { }
+
+    private void notifyListenersOfCall(Call call) {
+        synchronized (mListeners) {
+            for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
+                ITelephonyListener listener = it.next();
+                try {
+                    notifyListenerOfCallLocked(call, listener);
+                } catch (RemoteException e) {
+                    TelephonyListenerDeathRecipient deathRecipient =
+                            mDeathRecipients.get(listener.asBinder());
+                    if (deathRecipient != null) {
+                        deathRecipient.unlinkDeathRecipient();
+                    }
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    private void notifyListenerOfCallLocked(final Call call,final ITelephonyListener listener)
+            throws RemoteException {
+        if (Binder.isProxy(listener)) {
+            listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
+        } else {
+            mMainThreadHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    try {
+                        listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
+                    } catch (RemoteException e) {
+                        Log.wtf(LOG_TAG, "Local binder call failed with RemoteException.", e);
+                    }
+                }
+            });
+        }
+
+    }
+
+    private class TelephonyListenerDeathRecipient implements Binder.DeathRecipient {
+        private final IBinder mBinder;
+
+        public TelephonyListenerDeathRecipient(IBinder listener) {
+            mBinder = listener;
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                unlinkDeathRecipient();
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mListeners) {
+                if (mListeners.contains(mBinder)) {
+                    mListeners.remove(mBinder);
+                    Log.w(LOG_TAG, "ITelephonyListener died. Removing.");
+                } else {
+                    Log.w(LOG_TAG, "TelephonyListener binder died but the listener " +
+                            "is not registered.");
+                }
+            }
+        }
+
+        public void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+    }
 }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 2d44977..74a29e4 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -136,6 +136,10 @@
         }
     }
 
+    /** USSD information used to aggregate all USSD messages */
+    private static AlertDialog sUssdDialog = null;
+    private static StringBuilder sUssdMsg = new StringBuilder();
+
     /**
      * Handler that tracks the connections and updates the value of the
      * Mute settings for each connection as needed.
@@ -720,11 +724,6 @@
             // we dialed an MMI (see below).
         }
 
-        // Now that the call is successful, we can save the gateway info for the call
-        if (callGateway != null) {
-            callGateway.setGatewayInfoForConnection(connection, gatewayInfo);
-        }
-
         int phoneType = phone.getPhoneType();
 
         // On GSM phones, null is returned for MMI codes
@@ -736,6 +735,11 @@
                 status = CALL_STATUS_FAILED;
             }
         } else {
+            // Now that the call is successful, we can save the gateway info for the call
+            if (callGateway != null) {
+                callGateway.setGatewayInfoForConnection(connection, gatewayInfo);
+            }
+
             if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                 updateCdmaCallStateOnNewOutgoingCall(app, connection);
             }
@@ -833,6 +837,33 @@
         }
     }
 
+    static void swap() {
+        final PhoneGlobals mApp = PhoneGlobals.getInstance();
+        if (!okToSwapCalls(mApp.mCM)) {
+            // TODO: throw an error instead?
+            return;
+        }
+
+        // Swap the fg and bg calls.
+        // In the future we may provide some way for user to choose among
+        // multiple background calls, for now, always act on the first background call.
+        PhoneUtils.switchHoldingAndActive(mApp.mCM.getFirstActiveBgCall());
+
+        // If we have a valid BluetoothPhoneService then since CDMA network or
+        // Telephony FW does not send us information on which caller got swapped
+        // we need to update the second call active state in BluetoothPhoneService internally
+        if (mApp.mCM.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            final IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
+            if (btPhone != null) {
+                try {
+                    btPhone.cdmaSwapSecondCallState();
+                } catch (RemoteException e) {
+                    Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+                }
+            }
+        }
+    }
+
     /**
      * @param heldCall is the background call want to be swapped
      */
@@ -1100,18 +1131,33 @@
                 // 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.
-                AlertDialog newDialog = new AlertDialog.Builder(context)
-                        .setMessage(text)
-                        .setPositiveButton(R.string.ok, null)
-                        .setCancelable(true)
-                        .create();
 
-                newDialog.getWindow().setType(
-                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
-                newDialog.getWindow().addFlags(
-                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+                if (sUssdDialog == null) {
+                    sUssdDialog = new AlertDialog.Builder(context)
+                            .setPositiveButton(R.string.ok, null)
+                            .setCancelable(true)
+                            .setOnDismissListener(new DialogInterface.OnDismissListener() {
+                                @Override
+                                public void onDismiss(DialogInterface dialog) {
+                                    sUssdMsg.setLength(0);
+                                }
+                            })
+                            .create();
 
-                newDialog.show();
+                    sUssdDialog.getWindow().setType(
+                            WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+                    sUssdDialog.getWindow().addFlags(
+                            WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+                }
+                if (sUssdMsg.length() != 0) {
+                    sUssdMsg
+                            .insert(0, "\n")
+                            .insert(0, app.getResources().getString(R.string.ussd_dialog_sep))
+                            .insert(0, "\n");
+                }
+                sUssdMsg.insert(0, text);
+                sUssdDialog.setMessage(sUssdMsg.toString());
+                sUssdDialog.show();
             } else {
                 if (DBG) log("USSD code has requested user input. Constructing input dialog.");
 
@@ -1911,7 +1957,7 @@
         // in use.
         app.updateWakeState();
 
-        app.mCM.setEchoSuppressionEnabled(flag);
+        app.mCM.setEchoSuppressionEnabled();
     }
 
     /**