Merge the 2019-08-01 SPL branch from AOSP-Partner

* aosp-partner/security-aosp-nyc-mr2-release:
  Add flag to default dialer change dialog

Change-Id: Ie0a5d6dd0b2ddbe0146eb3c22ee1c8f6bac7b4a4
diff --git a/Android.mk b/Android.mk
index 79ef194..3308e04 100644
--- a/Android.mk
+++ b/Android.mk
@@ -3,9 +3,13 @@
 # Build the Telecom service.
 include $(CLEAR_VARS)
 
-LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_JAVA_LIBRARIES := telephony-common telephony-ext ims-common
+LOCAL_STATIC_JAVA_LIBRARIES := ims-ext-common
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under, proto)
+LOCAL_SRC_FILES += \
+       src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
+
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7ace0e0..5ef8e87 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -209,6 +209,7 @@
                 <action android:name="com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS" />
                 <action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
                 <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" />
+                <action android:name="org.codeaurora.ims.ACTION_CALL_PULL" />
             </intent-filter>
         </receiver>
 
diff --git a/res/drawable-hdpi/video_icon.png b/res/drawable-hdpi/video_icon.png
new file mode 100644
index 0000000..b4a4857
--- /dev/null
+++ b/res/drawable-hdpi/video_icon.png
Binary files differ
diff --git a/res/drawable-hdpi/volte_icon.png b/res/drawable-hdpi/volte_icon.png
new file mode 100644
index 0000000..0679d7b
--- /dev/null
+++ b/res/drawable-hdpi/volte_icon.png
Binary files differ
diff --git a/res/drawable-mdpi/video_icon.png b/res/drawable-mdpi/video_icon.png
new file mode 100644
index 0000000..b4a4857
--- /dev/null
+++ b/res/drawable-mdpi/video_icon.png
Binary files differ
diff --git a/res/drawable-mdpi/volte_icon.png b/res/drawable-mdpi/volte_icon.png
new file mode 100644
index 0000000..0679d7b
--- /dev/null
+++ b/res/drawable-mdpi/volte_icon.png
Binary files differ
diff --git a/res/drawable-xhdpi/video_icon.png b/res/drawable-xhdpi/video_icon.png
new file mode 100644
index 0000000..b4a4857
--- /dev/null
+++ b/res/drawable-xhdpi/video_icon.png
Binary files differ
diff --git a/res/drawable-xhdpi/volte_icon.png b/res/drawable-xhdpi/volte_icon.png
new file mode 100644
index 0000000..0679d7b
--- /dev/null
+++ b/res/drawable-xhdpi/volte_icon.png
Binary files differ
diff --git a/res/drawable-xxhdpi/video_icon.png b/res/drawable-xxhdpi/video_icon.png
new file mode 100644
index 0000000..b4a4857
--- /dev/null
+++ b/res/drawable-xxhdpi/video_icon.png
Binary files differ
diff --git a/res/drawable-xxhdpi/volte_icon.png b/res/drawable-xxhdpi/volte_icon.png
new file mode 100644
index 0000000..0679d7b
--- /dev/null
+++ b/res/drawable-xxhdpi/volte_icon.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/video_icon.png b/res/drawable-xxxhdpi/video_icon.png
new file mode 100644
index 0000000..b4a4857
--- /dev/null
+++ b/res/drawable-xxxhdpi/video_icon.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/volte_icon.png b/res/drawable-xxxhdpi/volte_icon.png
new file mode 100644
index 0000000..0679d7b
--- /dev/null
+++ b/res/drawable-xxxhdpi/volte_icon.png
Binary files differ
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 8374edb..13be3c0 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -67,4 +67,6 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"无法屏蔽紧急服务号码。"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> 已被屏蔽。"</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"使用个人拨号器拨打电话"</string>
+    <string name="headset_plugin_view_text">耳机已插入到手机。</string>
+    <string name="headset_plugin_view_title">耳机插入</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 0dff76b..072b723 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -67,4 +67,6 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"無法封鎖緊急電話號碼。"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"已封鎖 <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>。"</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"使用個人撥號器撥打電話"</string>
+    <string name="headset_plugin_view_text">耳機已插入到手機</string>
+    <string name="headset_plugin_view_title">耳機插入</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 7bb98a9..faf0328 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -67,4 +67,6 @@
     <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"無法封鎖緊急服務電話號碼。"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> 已在封鎖清單中。"</string>
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"使用個人撥號程式撥打電話"</string>
+    <string name="headset_plugin_view_text">耳機已插入到手機</string>
+    <string name="headset_plugin_view_title">耳機插入</string>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 7dd43f4..375ff91 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -42,6 +42,9 @@
     <!-- Flag indicating whether audio should be routed to speaker when docked -->
     <bool name="use_speaker_when_docked">true</bool>
 
+    <!-- DTMF key to be used for LCH hold tone -->
+    <string name="lch_dtmf_key" translatable="false">D</string>
+
     <!-- Flag indicating whether allow (silence rather than reject) the incoming call if it has a
          different source (connection service) from the existing ringing call when reaching
          maximum ringing calls. -->
diff --git a/res/values/qtistrings.xml b/res/values/qtistrings.xml
new file mode 100644
index 0000000..fa27ea6
--- /dev/null
+++ b/res/values/qtistrings.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+  ~
+  ~ Redistribution and use in source and binary forms, with or without
+  ~ modification, are permitted provided that the following conditions are
+  ~ met:
+  ~      Redistributions of source code must retain the above copyright
+  ~       notice, this list of conditions and the following disclaimer.
+  ~      Redistributions in binary form must reproduce the above
+  ~       copyright notice, this list of conditions and the following
+  ~       disclaimer in the documentation and/or other materials provided
+  ~       with the distribution.
+  ~      Neither the name of The Linux Foundation nor the names of its
+  ~       contributors may be used to endorse or promote products derived
+  ~       from this software without specific prior written permission.
+  ~
+  ~ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+  ~ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+  ~ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+  ~ ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+  ~ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  ~ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  ~ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+  ~ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+  ~ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  ~ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+  ~ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  ~
+  -->
+
+<!-- The xml contains Qti specific resource strings neede for any value added features. -->
+<resources>
+    <!-- Title of the VICE notifications -->
+    <string name="notification_pullcall">Calls on Secondary</string>
+    <!-- Message for "Call Pull"" Action, which is displayed in the Vice Notification
+         The user will be able to call back to the person or the phone number. -->
+    <string name="pull_to_call_back">Tap to pull</string>
+    <!-- Message to notify when incoming call is rejected due to low battery -->
+    <string name="incoming_call_failed_low_battery">Incoming Call is failed due to low battery.</string>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 11b2d50..1fdcf2d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -175,4 +175,8 @@
     <string name="handle_restricted">RESTRICTED</string>
 
     <string name="toast_personal_call_msg">Using the personal dialer to make the call</string>
+    <string name="toast_make_video_call_failed">Can not make video call, incorrect number format</string>
+
+    <string name="headset_plugin_view_text">Headset is plugged in.</string>
+    <string name="headset_plugin_view_title">Headset</string>
 </resources>
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 400ae66..c20e7fd 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -1,4 +1,7 @@
 /*
+ * Copyright (c) 2014, 2015 The Linux Foundation. All rights reserved.
+ * Not a contribution.
+ *
  * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,19 +33,30 @@
 import android.os.RemoteException;
 import android.telecom.Connection;
 import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.os.UserHandle;
+
+import android.telecom.TelecomManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.CallsManager.CallsManagerListener;
 
+import java.lang.NumberFormatException;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.codeaurora.btmultisim.IBluetoothDsdaService;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.content.ComponentName;
+
 /**
  * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
  * and accepts call-related commands to perform on behalf of the BT device.
@@ -55,6 +69,9 @@
                 PhoneAccountRegistrar phoneAccountRegistrar);
     }
 
+    private TelecomManager mTelecomManager = null;
+    private IBluetoothDsdaService mBluetoothDsda = null; //Handles DSDA Service.
+
     private static final String TAG = "BluetoothPhoneService";
 
     // match up with bthf_call_state_t of bt_hf.h
@@ -82,6 +99,9 @@
     private String mRingingAddress = null;
     private int mRingingAddressType = 0;
     private Call mOldHeldCall = null;
+    private static final int INVALID_SUBID = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private static final int[] LIVE_CALL_STATES =
+            {CallState.CONNECTING, CallState.DIALING, CallState.ACTIVE};
 
     /**
      * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
@@ -164,14 +184,30 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "getNetworkOperator");
+                    String label = null;
                     PhoneAccount account = getBestPhoneAccount();
-                    if (account != null && account.getLabel() != null) {
-                        return account.getLabel().toString();
+                    if (account != null) {
+                        PhoneAccountHandle ph = account.getAccountHandle();
+                        if (ph != null) {
+                            String subId = ph.getId();
+                            int sub;
+                            try {
+                                sub = Integer.parseInt(subId);
+                            } catch (NumberFormatException e){
+                                Log.w(this, " NumberFormatException " + e);
+                                sub = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                            }
+                            label = TelephonyManager.from(mContext)
+                                    .getNetworkOperatorName(sub);
+                        } else {
+                            Log.w(this, "Phone Account Handle is NULL");
+                        }
                     } else {
                         // Finally, just get the network name from telephony.
-                        return TelephonyManager.from(mContext)
+                        label = TelephonyManager.from(mContext)
                                 .getNetworkOperatorName();
                     }
+                    return label;
                 } finally {
                     Binder.restoreCallingIdentity(token);
                     Log.endSession();
@@ -240,7 +276,17 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "queryPhoneState");
-                    updateHeadsetWithCallState(true /* force */);
+                    if (isDsdaEnabled()) {
+                        if (mBluetoothDsda != null) {
+                            try {
+                                mBluetoothDsda.processQueryPhoneState();
+                            } catch (RemoteException e) {
+                                Log.i(TAG, "DSDA Service not found exception " + e);
+                            }
+                        }
+                    } else {
+                        updateHeadsetWithCallState(true /* force */, null);
+                    }
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
@@ -292,19 +338,27 @@
     public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
         @Override
         public void onCallAdded(Call call) {
+            Log.d(TAG, "onCallAdded");
             if (call.isExternalCall()) {
                 return;
             }
-            updateHeadsetWithCallState(false /* force */);
+            if (isDsdaEnabled() && call.isConference() &&
+                    (call.getChildCalls().size() == 0)) {
+                Log.d(TAG, "Ignore onCallAdded for new parent call" +
+                        " update headset when onIsConferencedChanged is called later");
+                return;
+            }
+            updateHeadsetWithCallState(false /* force */, call);
         }
 
         @Override
         public void onCallRemoved(Call call) {
+            Log.d(TAG, "onCallRemoved");
             if (call.isExternalCall()) {
                 return;
             }
             mClccIndexMap.remove(call);
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
 
         /**
@@ -325,9 +379,25 @@
 
         @Override
         public void onCallStateChanged(Call call, int oldState, int newState) {
+            Log.d(TAG, "onCallStateChanged, call: " + call + " oldState: " + oldState +
+                    " newState: " + newState);
             if (call.isExternalCall()) {
                 return;
             }
+            // If onCallStateChanged comes with oldState = newState when DSDA is enabled,
+            // check if the call is on ActiveSub. If so, this callback is called for
+            // Active Subscription change.
+            if (isDsdaEnabled() && (oldState == newState)) {
+                if (isCallonActiveSub(call)) {
+                    Log.d(TAG, "Active subscription changed");
+                    updateActiveSubChange();
+                    return;
+                } else {
+                    Log.d(TAG, "onCallStateChanged called without any call" +
+                            " state change for BG sub. Ignore updating HS");
+                    return;
+                }
+            }
             // If a call is being put on hold because of a new connecting call, ignore the
             // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
             // state atomically.
@@ -341,15 +411,20 @@
                 }
             }
 
-            // To have an active call and another dialing at the same time is an invalid BT
-            // state. We can assume that the active call will be automatically held which will
-            // send another update at which point we will be in the right state.
-            if (mCallsManager.getActiveCall() != null
-                    && oldState == CallState.CONNECTING &&
-                    (newState == CallState.DIALING || newState == CallState.PULLING)) {
-                return;
+            // To have an active call and another dialing at the same time on Active Sub is an
+            // invalid BT state. We can assume that the active call will be automatically held
+            // which will send another update at which point we will be in the right state.
+            Call anyActiveCall = mCallsManager.getActiveCall();
+            if ((anyActiveCall != null) && oldState == CallState.CONNECTING &&
+                    newState == CallState.DIALING || newState == CallState.PULLING) {
+                if (!isDsdaEnabled()) {
+                    return;
+                } else if (isCallonActiveSub(anyActiveCall)) {
+                    Log.d(TAG, "Dialing attempted on active sub when call is active");
+                    return;
+                }
             }
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
 
         @Override
@@ -370,6 +445,7 @@
              * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
              * Call 3) when there is actually only one active call (Call 3).
              */
+            Log.d(TAG, "onIsConferencedChanged");
             if (call.getParentCall() != null) {
                 // If this call is newly conferenced, ignore the callback. We only care about the
                 // one sent for the parent conference call.
@@ -383,7 +459,7 @@
                 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
                 return;
             }
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
     };
 
@@ -430,6 +506,15 @@
             }
         }
     };
+        public static final String ACTIVE_SUBSCRIPTION = "active_sub";
+        private boolean isCallonActiveSub(Call call){
+            boolean isActiveSub = false;
+            if (call.getExtras() != null &&
+                (call.getExtras().containsKey(ACTIVE_SUBSCRIPTION))){
+                isActiveSub = call.getExtras().getBoolean(ACTIVE_SUBSCRIPTION);
+            }
+            return isActiveSub;
+        }
 
     private BluetoothAdapterProxy mBluetoothAdapter;
     private BluetoothHeadsetProxy mBluetoothHeadset;
@@ -471,8 +556,20 @@
         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
 
+        mTelecomManager = (TelecomManager)context.getSystemService(Context.TELECOM_SERVICE);
+        if (mTelecomManager == null) {
+            Log.d(TAG, "BluetoothPhoneService shutting down, TELECOM_SERVICE found.");
+            return;
+        }
+
+        //Check whether we support DSDA or not
+        if (TelephonyManager.getDefault().isMultiSimEnabled()) {
+            Log.d(TAG, "DSDA is enabled, Bind to DSDA service");
+            createBtMultiSimService();
+        }
+
         mCallsManager.addListener(mCallsManagerListener);
-        updateHeadsetWithCallState(false /* force */);
+        updateHeadsetWithCallState(false /* force */, null);
     }
 
     @VisibleForTesting
@@ -480,7 +577,44 @@
         mBluetoothHeadset = bluetoothHeadset;
     }
 
+    private void createBtMultiSimService() {
+        Intent intent = new Intent(IBluetoothDsdaService.class.getName());
+        intent.setComponent(intent.resolveSystemService(mContext.getPackageManager(), 0));
+        if (intent.getComponent() == null || !mContext.bindService(intent,
+                btMultiSimServiceConnection, Context.BIND_AUTO_CREATE)) {
+            Log.i(TAG, "Ignoring IBluetoothDsdaService class not found exception ");
+        } else {
+            Log.d(TAG, "IBluetoothDsdaService bound request success");
+        }
+    }
+
+    private ServiceConnection btMultiSimServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            //Get handle to IBluetoothDsdaService.Stub.asInterface(service);
+            mBluetoothDsda = IBluetoothDsdaService.Stub.asInterface(service);
+            Log.d(TAG,"Dsda Service Connected" + mBluetoothDsda);
+            if (mBluetoothDsda != null) {
+                Log.i(TAG, "IBluetoothDsdaService created");
+            } else {
+                Log.i(TAG, "IBluetoothDsdaService Error");
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName arg0) {
+            Log.i(TAG,"DSDA Service onServiceDisconnected");
+            mBluetoothDsda = null;
+        }
+    };
+
     private boolean processChld(int chld) {
+        if (isDsdaEnabled() && (mBluetoothDsda != null)) {
+            try {
+                return processDsdaChld(chld);
+            } catch (RemoteException e) {
+                Log.i(TAG, " BluetoothDsdaService class not found exception " + e);
+            }
+        }
+
         Call activeCall = mCallsManager.getActiveCall();
         Call ringingCall = mCallsManager.getRingingCall();
         Call heldCall = mCallsManager.getHeldCall();
@@ -497,6 +631,8 @@
                 return true;
             }
         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
+            if (activeCall == null && ringingCall == null && heldCall == null)
+                return false;
             if (activeCall != null) {
                 mCallsManager.disconnectCall(activeCall);
                 if (ringingCall != null) {
@@ -506,11 +642,17 @@
                 }
                 return true;
             }
+            if (ringingCall != null) {
+                mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
+            } else if (heldCall != null) {
+                mCallsManager.unholdCall(heldCall);
+            }
+            return true;
         } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
             if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
                 activeCall.swapConference();
                 Log.i(TAG, "CDMA calls in conference swapped, updating headset");
-                updateHeadsetWithCallState(true /* force */);
+                updateHeadsetWithCallState(true /* force */, activeCall);
                 return true;
             } else if (ringingCall != null) {
                 mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
@@ -555,7 +697,15 @@
             if (!call.isConference() ||
                     (call.isConference() && call
                             .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
-                sendClccForCall(call, shouldLog);
+                if (isDsdaEnabled() && (mBluetoothDsda != null)) {
+                    try {
+                        sendDsdaClccForCall(call, shouldLog);
+                    } catch (RemoteException e) {
+                        Log.i(TAG, " BluetoothDsdaService class not found exception " + e);
+                    }
+                } else {
+                    sendClccForCall(call, shouldLog);
+                }
             }
         }
         sendClccEndMarker();
@@ -641,6 +791,154 @@
         }
     }
 
+    /**
+     * Sends a single clcc (C* List Current Calls) event for the specified call in DSDA scenario.
+     */
+    private void sendDsdaClccForCall(Call call, boolean shouldLog) throws  RemoteException {
+        boolean isForeground = mCallsManager.getForegroundCall() == call;
+        boolean isPartOfConference = false;
+        boolean isActive = false;
+        boolean allowDsda = false;
+        int state = convertCallState(call.getState(), isForeground);
+        int subForCall = Integer.parseInt(call.getTargetPhoneAccount().getId());
+        int activeSub = getActiveSubscription();
+        if (INVALID_SUBID == activeSub) {
+            Log.i(TAG, "Invalid activeSub id, returning");
+            return;
+        }
+
+        Call activeSubForegroundCall = mCallsManager.getFirstCallWithState(
+                Integer.toString(activeSub), LIVE_CALL_STATES);
+        Call activeSubRingingCall = mCallsManager.getFirstCallWithState(
+                Integer.toString(activeSub), CallState.RINGING);
+        Call activeSubBackgroundCall = mCallsManager.getFirstCallWithState(
+                Integer.toString(activeSub), CallState.ON_HOLD);
+
+        if (getBluetoothCallStateForUpdate() != CALL_STATE_IDLE) {
+            allowDsda = true;
+            Log.i(this, "Call setup in progress, allowDsda: " + allowDsda);
+        }
+
+        Log.i(this, "CLCC on SUB: " + subForCall + " CallState: " + state);
+
+        if (state == CALL_STATE_IDLE) {
+            return;
+        }
+
+        if (call == activeSubRingingCall) {
+            Log.i(this, "This is FG Ringing call on active sub");
+            isActive = true;
+        } else if (call == activeSubForegroundCall) {
+            Log.i(this, "This is FG live call on active sub");
+            isActive = true;
+        } else if (call == activeSubBackgroundCall) {
+            Log.i(this, "This is BG call on active sub");
+        }
+
+        Call conferenceCall = call.getParentCall();
+        if (conferenceCall != null) {
+            Log.i(this, "This call has parent call");
+            isPartOfConference = true;
+            Call activeChild = conferenceCall.getConferenceLevelActiveCall();
+            if (state == CALL_STATE_ACTIVE && activeChild != null) {
+                boolean shouldReevaluateState =
+                        conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
+                        (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
+                        !conferenceCall.wasConferencePreviouslyMerged());
+
+                Log.i(this, "shouldReevaluateState: " + shouldReevaluateState);
+                if (shouldReevaluateState) {
+                    isPartOfConference = false;
+                    if (call == activeChild) {
+                        Log.i(this, "this is active child");
+                        if ((mBluetoothDsda.hasCallsOnBothSubs() == true) &&
+                                isCallonActiveSub(activeChild)) {
+                            isActive = true;
+                        }
+                        state = CALL_STATE_ACTIVE;
+                    } else {
+                        Log.i(this, "this is not active child");
+                        if ((mBluetoothDsda.hasCallsOnBothSubs() == true) &&
+                             isCallonActiveSub(call)) {
+                            isActive = false;
+                        }
+                        state = CALL_STATE_HELD;
+                    }
+                }
+            }
+        }
+
+        if (call != null) {
+            if (mBluetoothDsda.isFakeMultiPartyCall() && !isActive) {
+                Log.i(this, "A fake mparty scenario");
+                isPartOfConference = true;
+            }
+        }
+
+        //Special case:
+        //Sub1: 1A(isPartOfConference=true), 2A(isPartOfConference=true)
+        //Sub2: 3A(isPartOfConference should set to true), 4W(isPartOfConference=false)
+        if ((mBluetoothDsda.hasCallsOnBothSubs() == true) && isCallonActiveSub(call)
+                && (activeSubRingingCall != null) && (call != activeSubRingingCall)) {
+            Log.i(this, "A fake mparty special scenario");
+            isPartOfConference = true;
+        }
+        Log.i(this, "call.isPartOfConference: " + isPartOfConference);
+
+        int index = getIndexForCall(call);
+        int direction = call.isIncoming() ? 1 : 0;
+        final Uri addressUri;
+        if (call.getGatewayInfo() != null) {
+            addressUri = call.getGatewayInfo().getOriginalAddress();
+        } else {
+            addressUri = call.getHandle();
+        }
+        String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
+        int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
+
+        if (mCallsManager.hasActiveOrHoldingCall()) {
+            if (state == CALL_STATE_INCOMING) {
+                Log.i(this, "hasActiveOrHoldingCall(), If Incoming, make it Waiting");
+                state = CALL_STATE_WAITING; //DSDA
+            }
+        }
+
+       // If calls on both Subs, need to change call states on BG calls
+       if (((mBluetoothDsda.hasCallsOnBothSubs() == true) || allowDsda) && !isActive) {
+           Log.i(this, "Calls on both Subs, manage call state for BG sub calls");
+           int activeCallState = convertCallState(
+                   (activeSubRingingCall != null) ? activeSubRingingCall.getState():
+                   CallState.NEW, (activeSubForegroundCall !=null) ?
+                   activeSubForegroundCall.getState(): CallState.NEW);
+           Log.i(this, "state : " + state + "activeCallState: " + activeCallState);
+           //Fake call held for all background calls
+           if ((state == CALL_STATE_ACTIVE) && (activeCallState != CALL_STATE_INCOMING)) {
+               state = CALL_STATE_HELD;
+           } else if (isPartOfConference == true) {
+               Log.i(this, "isPartOfConference, manage call states on BG sub");
+               if (activeCallState != CALL_STATE_INCOMING) {
+                   state = CALL_STATE_HELD;
+               } else if (activeCallState == CALL_STATE_INCOMING) {
+                   state = CALL_STATE_ACTIVE;
+               }
+           }
+           Log.i(this, "state of this BG Sub call: " + state);
+        }
+
+        if (shouldLog) {
+            Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
+                    index, direction, state, isPartOfConference, Log.piiHandle(address),
+                    addressType);
+        }
+
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(
+                    index, direction, state, 0, isPartOfConference, address, addressType);
+        } else {
+            Log.i(this, "headset null, no need to send clcc");
+        }
+    }
+
     private void sendClccEndMarker() {
         // End marker is recognized with an index value of 0. All other parameters are ignored.
         if (mBluetoothHeadset != null) {
@@ -667,89 +965,130 @@
         return i;
     }
 
+    private void updateActiveSubChange() {
+        Log.d(TAG, "update ActiveSubChange to DSDA service");
+        if (isDsdaEnabled() && (mBluetoothDsda != null)) {
+            try {
+                mBluetoothDsda.phoneSubChanged();
+            } catch (RemoteException e) {
+                Log.w(TAG, "DSDA class not found exception " + e);
+            }
+        }
+    }
+
     /**
      * Sends an update of the current call state to the current Headset.
      *
      * @param force {@code true} if the headset state should be sent regardless if no changes to the
      *      state have occurred, {@code false} if the state should only be sent if the state has
      *      changed.
+     * @ param call is specified call for which Headset is to be updated.
      */
-    private void updateHeadsetWithCallState(boolean force) {
-        Call activeCall = mCallsManager.getActiveCall();
-        Call ringingCall = mCallsManager.getRingingCall();
-        Call heldCall = mCallsManager.getHeldCall();
+    private void updateHeadsetWithCallState(boolean force, Call call) {
+        if (isDsdaEnabled() && (call != null)) {
+            Log.d(TAG, "DSDA call operation, handle it separately");
+            updateDsdaServiceWithCallState(call);
+        } else {
+            CallsManager callsManager = mCallsManager;
+            Call activeCall = mCallsManager.getActiveCall();
+            Call ringingCall = mCallsManager.getRingingCall();
+            Call heldCall = mCallsManager.getHeldCall();
 
-        int bluetoothCallState = getBluetoothCallStateForUpdate();
+            int bluetoothCallState = getBluetoothCallStateForUpdate();
 
-        String ringingAddress = null;
-        int ringingAddressType = 128;
-        if (ringingCall != null && ringingCall.getHandle() != null) {
-            ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
-            if (ringingAddress != null) {
-                ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
-            }
-        }
-        if (ringingAddress == null) {
-            ringingAddress = "";
-        }
-
-        int numActiveCalls = activeCall == null ? 0 : 1;
-        int numHeldCalls = mCallsManager.getNumHeldCalls();
-        // Intermediate state for GSM calls which are in the process of being swapped.
-        // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
-        //       are held?
-        boolean callsPendingSwitch = (numHeldCalls == 2);
-
-        // For conference calls which support swapping the active call within the conference
-        // (namely CDMA calls) we need to expose that as a held call in order for the BT device
-        // to show "swap" and "merge" functionality.
-        boolean ignoreHeldCallChange = false;
-        if (activeCall != null && activeCall.isConference() &&
-                !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
-            if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
-                // Indicate that BT device should show SWAP command by indicating that there is a
-                // call on hold, but only if the conference wasn't previously merged.
-                numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
-            } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
-                numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
-            }
-
-            for (Call childCall : activeCall.getChildCalls()) {
-                // Held call has changed due to it being combined into a CDMA conference. Keep
-                // track of this and ignore any future update since it doesn't really count as
-                // a call change.
-                if (mOldHeldCall == childCall) {
-                    ignoreHeldCallChange = true;
-                    break;
+            String ringingAddress = null;
+            int ringingAddressType = 128;
+            if (ringingCall != null && ringingCall.getHandle() != null) {
+                ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
+                if (ringingAddress != null) {
+                    ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
                 }
             }
-        }
+            if (ringingAddress == null) {
+                ringingAddress = "";
+            }
 
-        if (mBluetoothHeadset != null &&
-                (force ||
-                        (!callsPendingSwitch &&
-                                (numActiveCalls != mNumActiveCalls ||
-                                numHeldCalls != mNumHeldCalls ||
-                                bluetoothCallState != mBluetoothCallState ||
-                                !TextUtils.equals(ringingAddress, mRingingAddress) ||
-                                ringingAddressType != mRingingAddressType ||
-                                (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
+            int numActiveCalls = activeCall == null ? 0 : 1;
+            int numHeldCalls = callsManager.getNumHeldCalls();
+            boolean callsSwitched = (numHeldCalls == 2);
+            // For conference calls which support swapping the active call within the conference
+            // (namely CDMA calls) we need to expose that as a held call in order for the BT device
+            // to show "swap" and "merge" functionality.
+            boolean ignoreHeldCallChange = false;
+            if (activeCall != null && activeCall.isConference() &&
+                !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
+                if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
+                    // Indicate that BT device should show SWAP command by indicating that there
+                    // is a call on hold, but only if the conference wasn't previously merged.
+                    numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
+                } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
+                    numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
+                }
 
-            // If the call is transitioning into the alerting state, send DIALING first.
-            // Some devices expect to see a DIALING state prior to seeing an ALERTING state
-            // so we need to send it first.
-            boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
-                    bluetoothCallState == CALL_STATE_ALERTING;
+                for (Call childCall : activeCall.getChildCalls()) {
+                    // Held call has changed due to it being combined into a CDMA conference. Keep
+                    // track of this and ignore any future update since it doesn't really count as
+                    // a call change.
+                    if (mOldHeldCall == childCall) {
+                        ignoreHeldCallChange = true;
+                        break;
+                    }
+                }
+            }
 
-            mOldHeldCall = heldCall;
-            mNumActiveCalls = numActiveCalls;
-            mNumHeldCalls = numHeldCalls;
-            mBluetoothCallState = bluetoothCallState;
-            mRingingAddress = ringingAddress;
-            mRingingAddressType = ringingAddressType;
+            if (mBluetoothHeadset != null &&
+                    (numActiveCalls != mNumActiveCalls ||
+                    numHeldCalls != mNumHeldCalls ||
+                    bluetoothCallState != mBluetoothCallState ||
+                    !TextUtils.equals(ringingAddress, mRingingAddress) ||
+                    ringingAddressType != mRingingAddressType ||
+                    (heldCall != mOldHeldCall && !ignoreHeldCallChange) ||
+                    force) && !callsSwitched) {
 
-            if (sendDialingFirst) {
-                // Log in full to make logs easier to debug.
+                // If the call is transitioning into the alerting state, send DIALING first.
+                // Some devices expect to see a DIALING state prior to seeing an ALERTING state
+                // so we need to send it first.
+                boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
+                        bluetoothCallState == CALL_STATE_ALERTING;
+
+                if (numActiveCalls > 0) {
+                Log.i(TAG, "updateHeadsetWithCallState: Call active");
+                boolean isCsCall = ((activeCall != null) &&
+                        !(activeCall.hasProperty(Connection.PROPERTY_HIGH_DEF_AUDIO) ||
+                        activeCall.hasProperty(Connection.PROPERTY_WIFI)));
+                final Intent intent = new Intent(TelecomManager.ACTION_CALL_TYPE);
+                intent.putExtra(TelecomManager.EXTRA_CALL_TYPE_CS, isCsCall);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+                mOldHeldCall = heldCall;
+                mNumActiveCalls = numActiveCalls;
+                mNumHeldCalls = numHeldCalls;
+                mBluetoothCallState = bluetoothCallState;
+                mRingingAddress = ringingAddress;
+                mRingingAddressType = ringingAddressType;
+
+                if (sendDialingFirst) {
+                    // Log in full to make logs easier to debug.
+                    Log.i(TAG, "updateHeadsetWithCallState " +
+                            "numActive %s, " +
+                            "numHeld %s, " +
+                            "callState %s, " +
+                            "ringing number %s, " +
+                            "ringing type %s",
+                            mNumActiveCalls,
+                            mNumHeldCalls,
+                            CALL_STATE_DIALING,
+                            Log.pii(mRingingAddress),
+                            mRingingAddressType);
+                    mBluetoothHeadset.phoneStateChanged(
+                            mNumActiveCalls,
+                            mNumHeldCalls,
+                            CALL_STATE_DIALING,
+                            mRingingAddress,
+                            mRingingAddressType);
+                }
+
+
                 Log.i(TAG, "updateHeadsetWithCallState " +
                         "numActive %s, " +
                         "numHeld %s, " +
@@ -758,40 +1097,156 @@
                         "ringing type %s",
                         mNumActiveCalls,
                         mNumHeldCalls,
-                        CALL_STATE_DIALING,
+                        mBluetoothCallState,
                         Log.pii(mRingingAddress),
                         mRingingAddressType);
+
                 mBluetoothHeadset.phoneStateChanged(
                         mNumActiveCalls,
                         mNumHeldCalls,
-                        CALL_STATE_DIALING,
+                        mBluetoothCallState,
                         mRingingAddress,
                         mRingingAddressType);
+
+                mHeadsetUpdatedRecently = true;
             }
-
-            Log.i(TAG, "updateHeadsetWithCallState " +
-                    "numActive %s, " +
-                    "numHeld %s, " +
-                    "callState %s, " +
-                    "ringing number %s, " +
-                    "ringing type %s",
-                    mNumActiveCalls,
-                    mNumHeldCalls,
-                    mBluetoothCallState,
-                    Log.pii(mRingingAddress),
-                    mRingingAddressType);
-
-            mBluetoothHeadset.phoneStateChanged(
-                    mNumActiveCalls,
-                    mNumHeldCalls,
-                    mBluetoothCallState,
-                    mRingingAddress,
-                    mRingingAddressType);
-
-            mHeadsetUpdatedRecently = true;
         }
     }
 
+    /**
+     * Sends an update of the current dsda call state to the Dsda service.
+     *
+     * @param call is specified call for which Headset is to be updated.
+     */
+    private void updateDsdaServiceWithCallState(Call call) {
+        CallsManager callsManager = mCallsManager;
+        int subscription = INVALID_SUBID;
+        if (mBluetoothDsda != null) {
+            Log.d(TAG, "Get the Sub on which call state change happened");
+            if (call.getTargetPhoneAccount() != null) {
+                String sub = call.getTargetPhoneAccount().getId();
+                subscription = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                try {
+                    subscription = Integer.parseInt(sub);
+                } catch (NumberFormatException e) {
+                    Log.w(this, " NumberFormatException " + e);
+                }
+            } else if (call.isConference()) {
+                for (Call childCall : call.getChildCalls()) {
+                    if (childCall.getTargetPhoneAccount() != null) {
+                        String sub = childCall.getTargetPhoneAccount().getId();
+                        subscription = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                        try {
+                            subscription = Integer.parseInt(sub);
+                        } catch (NumberFormatException e) {
+                            Log.w(this, " NumberFormatException " + e);
+                        }
+                    } else {
+                        Log.w(this, "PhoneAccountHandle is NULL for childCall: " + childCall);
+                    }
+                    if (subscription != INVALID_SUBID)
+                        break;
+                }
+            } else {
+                Log.w(this, "PhoneAccountHandle is NULL");
+            }
+
+            Log.d(TAG, "SUB on which call state to be updated " + subscription);
+            if (subscription == INVALID_SUBID) {
+                return;
+            }
+
+            try {
+                mBluetoothDsda.setCurrentSub(subscription);
+                Call ringCall = callsManager.getFirstCallWithState(
+                        Integer.toString(subscription), CallState.RINGING);
+                int ringingCallState = callsManager.hasRingingCall() ?
+                        CallState.RINGING : CallState.NEW;
+
+                Call foregroundCall = callsManager.getFirstCallWithState(
+                        Integer.toString(subscription), LIVE_CALL_STATES);
+                int foregroundCallState = (foregroundCall != null) ?
+                        foregroundCall.getState() : CallState.NEW;
+
+                Call backgroundCall = callsManager.getFirstCallWithState(
+                        Integer.toString(subscription), CallState.ON_HOLD);
+                int backgroundCallState = (backgroundCall != null) ?
+                        CallState.ON_HOLD: CallState.NEW;
+
+                Log.d(TAG, "callsManager.getActiveCall()  =  " + callsManager.getActiveCall());
+                int numHeldCallsonSub = getDsdaNumHeldCalls(callsManager.getActiveCall(),
+                        subscription, mOldHeldCall);
+
+                String ringingAddress = null;
+                int ringingAddressType = 128;
+                if (ringCall != null) {
+                    ringingAddress = ringCall.getHandle().getSchemeSpecificPart();
+                    if (ringingAddress != null) {
+                        ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
+                    }
+                }
+                if (ringingAddress == null) {
+                    ringingAddress = "";
+                }
+                mBluetoothDsda.handleMultiSimPreciseCallStateChange(foregroundCallState,
+                        ringingCallState, ringingAddress, ringingAddressType,
+                        backgroundCallState, numHeldCallsonSub);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Ignoring DSDA class not found exception " + e);
+            }
+        }
+        return;
+    }
+
+    private int getDsdaNumHeldCalls(Call activeCall, int subscription, Call mOldHeldCall) {
+        // For conference calls which support swapping the active call within the conference
+        // (namely CDMA calls) we need to expose that as a held call in order for the BT device
+        // to show "swap" and "merge" functionality.
+        int numHeldCalls = 0;
+        int activeCallSub = 0;
+
+        if (activeCall != null && activeCall.isConference()) {
+            if (activeCall.getTargetPhoneAccount() != null) {
+                String sub = activeCall.getTargetPhoneAccount().getId();
+                activeCallSub = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                try {
+                    activeCallSub = Integer.parseInt(sub);
+                } catch (NumberFormatException e) {
+                    Log.w(this, " NumberFormatException " + e);
+                }
+            } else {
+                for (Call childCall : activeCall.getChildCalls()) {
+                    if (childCall.getTargetPhoneAccount() != null) {
+                        String sub = childCall.getTargetPhoneAccount().getId();
+                        activeCallSub = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                        try {
+                            activeCallSub = Integer.parseInt(sub);
+                        } catch (NumberFormatException e) {
+                            Log.w(this, " NumberFormatException " + e);
+                        }
+                    } else {
+                        Log.w(this, "PhoneAccountHandle is NULL for childCall: " + childCall);
+                    }
+                    if (activeCallSub != INVALID_SUBID)
+                        break;
+                }
+            }
+            if (activeCallSub == subscription) {
+                if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
+                    // Indicate that BT device should show SWAP command by indicating that there
+                    // is a call on hold, but only if the conference wasn't previously merged.
+                    numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
+                } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
+                    numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
+                }
+                Log.i(TAG, "getDsdaNumHeldCalls: numHeldCalls:  " + numHeldCalls);
+                return numHeldCalls;
+            }
+        }
+        numHeldCalls = getNumCallsWithState(Integer.toString(subscription), CallState.ON_HOLD);
+        Log.i(TAG, "getDsdaNumHeldCalls: numHeldCalls = " + numHeldCalls);
+        return numHeldCalls;
+    }
     private int getBluetoothCallStateForUpdate() {
         CallsManager callsManager = mCallsManager;
         Call ringingCall = mCallsManager.getRingingCall();
@@ -815,6 +1270,15 @@
         return bluetoothCallState;
     }
 
+    private int convertCallState(int ringingState, int foregroundState) {
+        if (ringingState == CallState.RINGING)
+            return CALL_STATE_INCOMING;
+        else if (foregroundState == CallState.DIALING)
+            return CALL_STATE_ALERTING;
+        else
+            return CALL_STATE_IDLE;
+    }
+
     private int convertCallState(int callState, boolean isForegroundCall) {
         switch (callState) {
             case CallState.NEW:
@@ -878,4 +1342,196 @@
         }
         return account;
     }
+
+    private boolean isDsdaEnabled() {
+        //Check whether we support DSDA or not
+        if ((TelephonyManager.getDefault().getMultiSimConfiguration()
+                == TelephonyManager.MultiSimVariants.DSDA)) {
+            Log.d(TAG, "DSDA is enabled");
+            return true;
+        }
+        return false;
+    }
+
+    private int getNumCallsWithState(String subId, int state) {
+        int count = 0;
+        CallsManager callsManager = mCallsManager;
+        for (Call call : callsManager.getCalls()) {
+            if (call.getState() == state && call.getTargetPhoneAccount() != null
+                    && call.getTargetPhoneAccount().getId().equals(subId)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private int getActiveSubscription() {
+        String activeSub = mCallsManager.getActiveSubscription();
+        return (activeSub == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID:
+            Integer.parseInt(activeSub);
+    }
+
+    private boolean processDsdaChld(int chld) throws  RemoteException {
+        boolean status = true;
+        CallsManager callsManager = mCallsManager;
+        Log.i(TAG, "processDsdaChld: " + chld );
+        int activeSub = getActiveSubscription();
+        Log.i(TAG, "activeSub: " + activeSub);
+        if (INVALID_SUBID == activeSub) {
+            Log.i(TAG, "Invalid activeSub id, returning");
+            return false;
+        }
+
+        Call activeCall = callsManager.getActiveCall();
+        Call ringingCall = callsManager.getFirstCallWithState(
+                Integer.toString(activeSub), CallState.RINGING);
+        Call backgroundCall = callsManager.getFirstCallWithState(
+                Integer.toString(activeSub), CallState.ON_HOLD);
+
+        switch (chld) {
+            case CHLD_TYPE_RELEASEHELD:
+                if (ringingCall != null) {
+                    callsManager.rejectCall(ringingCall, false, null);
+                } else {
+                    Call call = getCallOnOtherSub(false);
+                    if (call != null) {
+                        callsManager.disconnectCall(call);
+                    } else {
+                        if (backgroundCall != null) {
+                            callsManager.disconnectCall(backgroundCall);
+                        }
+                    }
+                }
+                status = true;
+                break;
+
+            case CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD:
+                Call call = getCallOnOtherSub(false);
+                if ((ringingCall != null) && (call != null)) {
+                    //first answer the incoming call
+                    callsManager.answerCall(ringingCall, 0);
+                    //Try to Drop the call on the other SUB.
+                    callsManager.disconnectCall(call);
+                } else if (mBluetoothDsda.isSwitchSubAllowed()) {
+                    /* In case of Sub1=Active and Sub2=lch/held, drop call
+                       on active  Sub */
+                    Log.i(TAG, "processChld drop the call on Active sub, move LCH to active");
+                    call = getCallOnOtherSub(true);
+                    if (call != null) {
+                        callsManager.disconnectCall(call);
+                    }
+                } else {
+                    if (activeCall != null) {
+                        Log.i(TAG, "Dropping active call");
+                        callsManager.disconnectCall(activeCall);
+                        if (ringingCall != null) {
+                            callsManager.answerCall(ringingCall, 0);
+                        } else if (backgroundCall != null) {
+                            callsManager.unholdCall(backgroundCall);
+                        }
+                    }
+                }
+                status = true;
+                break;
+
+            case CHLD_TYPE_HOLDACTIVE_ACCEPTHELD:
+                if (mBluetoothDsda.canDoCallSwap()) {
+                    Log.i(TAG, "Try to do call swap on same sub");
+                    if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
+                        activeCall.swapConference();
+                    } else if (backgroundCall != null) {
+                        callsManager.unholdCall(backgroundCall);
+                    }
+                } else if (mBluetoothDsda.isSwitchSubAllowed()) {
+                    /*Switch SUB*/
+                    Log.i(TAG, "Switch sub");
+                    // If there is a change in active subscription while both the
+                    // subscriptions are in active state, need to siwtch the
+                    // playing of LCH/SCH tone to new LCH subscription.
+                    mBluetoothDsda.switchSub();
+                } else if (mBluetoothDsda.answerOnThisSubAllowed() == true) {
+                    Log.i(TAG, "Can we answer the call on other SUB?");
+                    /* Answer the call on current SUB*/
+                    if (ringingCall != null)
+                        callsManager.answerCall(ringingCall, 0);
+                } else {
+                    Log.i(TAG, "CHLD=2, Answer the call on same sub");
+                    if (ringingCall != null) {
+                        Log.i(TAG, "Background is on hold when incoming call came");
+                        callsManager.answerCall(ringingCall, 0);
+                    } else if (backgroundCall != null) {
+                        callsManager.unholdCall(backgroundCall);
+                    } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
+                        Log.i(TAG, "Only active call, put that to hold");
+                        callsManager.holdCall(activeCall);
+                    }
+                }
+                status = true;
+                break;
+
+            case CHLD_TYPE_ADDHELDTOCONF:
+                if (activeCall != null) {
+                    if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
+                        activeCall.mergeConference();
+                    } else {
+                        List<Call> conferenceable = activeCall.getConferenceableCalls();
+                        if (!conferenceable.isEmpty()) {
+                            callsManager.conference(activeCall, conferenceable.get(0));
+                        }
+                    }
+                }
+                status = true;
+                break;
+
+            default:
+                Log.i(TAG, "bad CHLD value: " + chld);
+                status = false;
+                break;
+        }
+        return status;
+    }
+
+    /* Get the active or held call on other Sub. */
+    private Call getCallOnOtherSub(boolean isActive) throws  RemoteException {
+        CallsManager callsManager = mCallsManager;
+        Log.d(TAG, "getCallOnOtherSub, isActiveSub call required: " + isActive);
+        int activeSub = getActiveSubscription();
+        if (INVALID_SUBID == activeSub) {
+            Log.i(TAG, "Invalid activeSub id, returning");
+            return null;
+        }
+        int bgSub = getOtherActiveSub(activeSub);
+        /*bgSub would be INVALID_SUBID when bg subscription has no calls*/
+        if (bgSub == INVALID_SUBID) {
+            return null;
+        }
+
+        Call call = null;
+        if (isActive) {
+            if (mBluetoothDsda.getTotalCallsOnSub(bgSub) < 2 ) {
+                if (callsManager.hasActiveOrHoldingCall(Integer.toString(activeSub)))
+                    call = callsManager.getFirstCallWithState(
+                            Integer.toString(activeSub), CallState.ACTIVE, CallState.ON_HOLD);
+            }
+        } else {
+            if (mBluetoothDsda.getTotalCallsOnSub(bgSub) == 1) {
+                if (callsManager.hasActiveOrHoldingCall(Integer.toString(bgSub)))
+                    call = callsManager.getFirstCallWithState(
+                            Integer.toString(bgSub), CallState.ACTIVE, CallState.ON_HOLD);
+            }
+        }
+        return call;
+    }
+
+    private int getOtherActiveSub(int activeSub) {
+        boolean subSwitched = false;
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            int[] subId = SubscriptionManager.getSubId(i);
+            if (subId[0] != activeSub) {
+                Log.i(TAG, "other Sub: " + subId[0]);
+                return subId[0];
+            }
+        }
+        return INVALID_SUBID;
+    }
 }
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 99a1c8c..c012927 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -210,7 +210,7 @@
     /**
      * The post-dial digits that were dialed after the network portion of the number
      */
-    private final String mPostDialDigits;
+    private String mPostDialDigits;
 
     /**
      * The secondary line number that an incoming call has been received on if the SIM subscription
@@ -274,6 +274,8 @@
 
     private boolean mSpeakerphoneOn;
 
+    private boolean mIsChildCall = false;
+
     /**
      * Tracks the video states which were applicable over the duration of a call.
      * See {@link VideoProfile} for a list of valid video states.
@@ -749,6 +751,10 @@
         return mHandle;
     }
 
+    public void setPostDialDigits(String postDialDigits) {
+        mPostDialDigits = postDialDigits;
+    }
+
     public String getPostDialDigits() {
         return mPostDialDigits;
     }
@@ -792,9 +798,8 @@
             // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
             // call, it will remain so for the rest of it's lifetime.
             if (!mIsEmergencyCall) {
-                mIsEmergencyCall = mHandle != null &&
-                        mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
-                                mHandle.getSchemeSpecificPart());
+                mIsEmergencyCall = mHandle != null && TelephonyUtil.isLocalEmergencyNumber(
+                        mHandle.getSchemeSpecificPart());
             }
             startCallerInfoLookup();
             for (Listener l : mListeners) {
@@ -1000,6 +1005,10 @@
         mCreationTimeMillis = time;
     }
 
+    public void setConnectTimeMillis(long connectTimeMillis) {
+        mConnectTimeMillis = connectTimeMillis;
+    }
+
     long getConnectTimeMillis() {
         return mConnectTimeMillis;
     }
@@ -1103,6 +1112,19 @@
         return mWasConferencePreviouslyMerged;
     }
 
+    public boolean isChildCall() {
+        return mIsChildCall;
+    }
+
+    /**
+     * Sets whether this call is a child call.
+     */
+    private void maybeSetCallAsChild() {
+        if (mParentCall != null) {
+            mIsChildCall = true;
+        }
+    }
+
     @VisibleForTesting
     public Call getConferenceLevelActiveCall() {
         return mConferenceLevelActiveCall;
@@ -1326,6 +1348,7 @@
 
         // Track that the call is now locally disconnecting.
         setLocallyDisconnecting(true);
+        maybeSetCallAsChild();
 
         if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                 mState == CallState.CONNECTING) {
@@ -1620,6 +1643,14 @@
         }
     }
 
+    void addParticipantWithConference(String recipients) {
+        if (mConnectionService == null) {
+            Log.w(this, "conference requested on a call without a connection service.");
+        } else {
+            mConnectionService.addParticipantWithConference(this, recipients);
+        }
+    }
+
     @VisibleForTesting
     public void mergeConference() {
         if (mConnectionService == null) {
@@ -1893,7 +1924,7 @@
             return;
         }
 
-        if (!handle.equals(mHandle)) {
+        if ((handle != null) && !handle.equals(mHandle)) {
             Log.i(this, "setCallerInfo received stale caller info for an old handle. Ignoring.");
             return;
         }
@@ -1901,8 +1932,8 @@
         mCallerInfo = callerInfo;
         Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
 
-        if (mCallerInfo.contactDisplayPhotoUri == null ||
-                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
+        if ((mCallerInfo != null) && (mCallerInfo.contactDisplayPhotoUri == null ||
+                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null)) {
             for (Listener l : mListeners) {
                 l.onCallerInfoChanged(this);
             }
@@ -1969,14 +2000,17 @@
     public void setVideoProvider(IVideoProvider videoProvider) {
         Log.v(this, "setVideoProvider");
 
+        if (mVideoProviderProxy != null) {
+            mVideoProviderProxy.clearVideoCallback();
+            mVideoProviderProxy = null;
+        }
+
         if (videoProvider != null ) {
             try {
                 mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
             } catch (RemoteException ignored) {
                 // Ignore RemoteException.
             }
-        } else {
-            mVideoProviderProxy = null;
         }
 
         mVideoProvider = videoProvider;
@@ -2063,7 +2097,8 @@
     }
 
     public boolean getIsVoipAudioMode() {
-        return mIsVoipAudioMode;
+            return mIsVoipAudioMode ||((mHandle != null) ?
+                    (mHandle.getScheme() == PhoneAccount.SCHEME_SIP): false);
     }
 
     public void setIsVoipAudioMode(boolean audioModeIsVoip) {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index d7336cd..7f54529 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -217,18 +217,16 @@
         // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
 
         if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
-            if (mForegroundCall == call) {
-                Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
-                        "an active in-call audio state before connection service has " +
-                        "connected the call.");
-                if (mCallStateToCalls.get(call.getState()) != null) {
-                    mCallStateToCalls.get(call.getState()).remove(call);
-                }
-                mActiveDialingOrConnectingCalls.add(call);
-                mCallAudioModeStateMachine.sendMessageWithArgs(
-                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
-                        makeArgsForModeStateMachine());
+            Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
+                    "an active in-call audio state before connection service has " +
+                    "connected the call.");
+            if (mCallStateToCalls.get(call.getState()) != null) {
+                mCallStateToCalls.get(call.getState()).remove(call);
             }
+            mActiveDialingOrConnectingCalls.add(call);
+            mCallAudioModeStateMachine.sendMessageWithArgs(
+                    CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
+                    makeArgsForModeStateMachine());
         }
 
         // Turn off mute when a new incoming call is answered.
@@ -262,6 +260,30 @@
     }
 
     /**
+     * Handles session modification requests sent
+     *
+     * @param fromProfile The video properties prior to the request.
+     * @param toProfile The video properties with the requested changes made.
+     */
+    @Override
+    public void onSessionModifyRequestSent(VideoProfile fromProfile, VideoProfile toProfile) {
+        Log.d(LOG_TAG, "onSessionModifyRequestSent : fromProfile = " + fromProfile +
+                " toProfile = " + toProfile);
+
+        if (toProfile == null) {
+            return;
+        }
+
+        final int videoState = toProfile.getVideoState();
+
+        final int fallbackAudioRoute = VideoProfile.isVideo(videoState) ?
+                CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_EARPIECE;
+        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                        CallAudioRouteStateMachine.SET_FALLBACK_AUDIO_ROUTE_HINT,
+                        fallbackAudioRoute);
+    }
+
+    /**
      * Play or stop a call hold tone for a call.  Triggered via
      * {@link Connection#sendConnectionEvent(String)} when the
      * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
@@ -311,7 +333,10 @@
         } else {
             // The call joined a conference, so stop tracking it.
             if (mCallStateToCalls.get(call.getState()) != null) {
-                mCallStateToCalls.get(call.getState()).remove(call);
+                boolean isRemoveCallSuccess = mCallStateToCalls.get(call.getState()).remove(call);
+                if (!isRemoveCallSuccess) {
+                    mActiveDialingOrConnectingCalls.remove(call);
+                }
             }
 
             updateForegroundCall();
@@ -360,6 +385,10 @@
         return null;
     }
 
+    public boolean hasAnyAliveCalls() {
+        return !mCallsManager.hasOnlyDisconnectedCalls();
+    }
+
     void toggleMute() {
         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.TOGGLE_MUTE);
@@ -587,6 +616,22 @@
         }
     }
 
+    //give preference to call on Active sub
+    private Call getCallFromList(LinkedHashSet<Call> list) {
+        Call CallInActiveSub = null;
+        for (Call call : list) {
+            String subId = (call.getTargetPhoneAccount() == null) ? null :
+                    call.getTargetPhoneAccount().getId();
+            if (subId != null && subId.equals(mCallsManager.getActiveSubscription())) {
+                CallInActiveSub = call;
+            }
+        }
+        if (CallInActiveSub == null) {
+            CallInActiveSub = list.iterator().next();
+        }
+        return CallInActiveSub;
+    }
+
     private void updateForegroundCall() {
         Call oldForegroundCall = mForegroundCall;
         if (mActiveDialingOrConnectingCalls.size() > 0) {
@@ -597,12 +642,15 @@
                     possibleConnectingCall = call;
                 }
             }
-            mForegroundCall = possibleConnectingCall == null ?
-                    mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
+            if (possibleConnectingCall != null) {
+                mForegroundCall = possibleConnectingCall;
+            } else {
+                mForegroundCall = getCallFromList(mActiveDialingOrConnectingCalls);
+            }
         } else if (mRingingCalls.size() > 0) {
-            mForegroundCall = mRingingCalls.iterator().next();
+            mForegroundCall = getCallFromList(mRingingCalls);
         } else if (mHoldingCalls.size() > 0) {
-            mForegroundCall = mHoldingCalls.iterator().next();
+            mForegroundCall = getCallFromList(mHoldingCalls);
         } else {
             mForegroundCall = null;
         }
@@ -759,4 +807,4 @@
     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
         return mCallStateToCalls;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 57043bc..e1775f0 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -146,7 +146,7 @@
     private class UnfocusedState extends BaseState {
         @Override
         public void enter() {
-            if (mIsInitialized) {
+            if (mIsInitialized && !mCallAudioManager.hasAnyAliveCalls()) {
                 Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
                 mAudioManager.abandonAudioFocusForCall();
                 mAudioManager.setMode(AudioManager.MODE_NORMAL);
@@ -378,7 +378,9 @@
                     // Do nothing.
                     return HANDLED;
                 case NEW_ACTIVE_OR_DIALING_CALL:
-                    // Do nothing. Already active.
+                    if (!args.foregroundCallIsVoip) {
+                        transitionTo(mSimCallFocusState);
+                    }
                     return HANDLED;
                 case NEW_RINGING_CALL:
                     // Don't make a call ring over an active call, but do play a call waiting tone.
@@ -436,8 +438,13 @@
                             ? mVoipCallFocusState : mSimCallFocusState);
                     return HANDLED;
                 case NEW_RINGING_CALL:
-                    // Apparently this is current behavior. Should this be the case?
-                    transitionTo(mRingingFocusState);
+                    if (args.hasHoldingCalls) {
+                        // Don't make a call ring over a held call, but do play
+                        // a call waiting tone.
+                        mCallAudioManager.startCallWaiting();
+                    } else {
+                        transitionTo(mRingingFocusState);
+                    }
                     return HANDLED;
                 case NEW_HOLDING_CALL:
                     // Do nothing.
@@ -528,4 +535,4 @@
             return mUnfocusedState;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f88475a..9622ee9 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -100,6 +100,7 @@
     public static final int USER_SWITCH_BASELINE_ROUTE = 1105;
 
     public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
+    public static final int SET_FALLBACK_AUDIO_ROUTE_HINT = 1202;
 
     public static final int MUTE_ON = 3001;
     public static final int MUTE_OFF = 3002;
@@ -122,6 +123,8 @@
         put(CallAudioState.ROUTE_WIRED_HEADSET, Log.Events.AUDIO_ROUTE_HEADSET);
     }};
 
+    private static final int INVALID_AUDIO_FALLBACK_HINT = -1;
+
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
         put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
         put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
@@ -235,6 +238,9 @@
         public void exit() {
             Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
                     "Leaving state " + getName());
+
+            // Reset fallback audio hint on exiting a state
+            mAudioRouteFallbackHint = INVALID_AUDIO_FALLBACK_HINT;
             super.exit();
         }
 
@@ -277,6 +283,9 @@
                 case SWITCH_FOCUS:
                     mAudioFocusType = msg.arg1;
                     return NOT_HANDLED;
+                case SET_FALLBACK_AUDIO_ROUTE_HINT:
+                    mAudioRouteFallbackHint = msg.arg1;
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -647,11 +656,7 @@
                     // No change in audio route required
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
-                    if (mWasOnSpeaker) {
-                        sendInternalMessage(SWITCH_SPEAKER);
-                    } else {
-                        sendInternalMessage(SWITCH_BASELINE_ROUTE);
-                    }
+                    sendInternalMessage(SWITCH_BASELINE_ROUTE);
                     return HANDLED;
                 case BT_AUDIO_DISCONNECT:
                     // This may be sent as a confirmation by the BT stack after switch off BT.
@@ -682,7 +687,10 @@
         @Override
         public void enter() {
             super.enter();
-            setSpeakerphoneOn(false);
+            // Don't close speaker to avoid Audio selects headset
+            // for a breif time with priority
+            // Need hide the speaker icon
+            mStatusBarNotifier.notifySpeakerphone(false);
             setBluetoothOn(true);
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
                     mAvailableRoutes);
@@ -915,8 +923,11 @@
                     // in the bluetooth route.
                     return HANDLED;
                 case DISCONNECT_BLUETOOTH:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE);
                     mWasOnSpeaker = false;
+                    // Reset fallback audio hint on disconnecting BT as we should fallback to
+                    // EARPIECE.
+                    mAudioRouteFallbackHint = INVALID_AUDIO_FALLBACK_HINT;
+                    sendInternalMessage(SWITCH_BASELINE_ROUTE);
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
                     updateSystemAudioState();
@@ -1148,6 +1159,12 @@
     private boolean mIsMuted;
     private boolean mAreNotificationSuppressed = false;
 
+    /**
+     * Hint to the state machine that the fallback audio route may have changed.
+     * It's up to the state machine to honor or ignore the hint.
+     */
+    private int mAudioRouteFallbackHint = INVALID_AUDIO_FALLBACK_HINT;
+
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final AudioManager mAudioManager;
@@ -1363,6 +1380,8 @@
             mAudioManager.setSpeakerphoneOn(on);
             mStatusBarNotifier.notifySpeakerphone(on);
         }
+        // StatusBarNotifier has check for previous state before notifying again
+        mStatusBarNotifier.notifySpeakerphone(on);
     }
 
     private void setBluetoothOn(boolean on) {
@@ -1543,7 +1562,34 @@
         return true;
     }
 
+    private boolean isFallbackAudioHintValid() {
+        return (mAudioRouteFallbackHint != INVALID_AUDIO_FALLBACK_HINT);
+    }
+
+    private int getBaselineFallbackRoute(int audioRouteFallbackHint) {
+        switch (audioRouteFallbackHint) {
+            case ROUTE_EARPIECE:
+                return SWITCH_EARPIECE;
+            case ROUTE_SPEAKER:
+                return SWITCH_SPEAKER;
+            case ROUTE_WIRED_HEADSET:
+                return SWITCH_HEADSET;
+            case ROUTE_BLUETOOTH:
+                return SWITCH_BLUETOOTH;
+            default:
+                return SWITCH_EARPIECE;
+        }
+    }
+
     private int calculateBaselineRouteMessage(boolean isExplicitUserRequest) {
+        if (!isExplicitUserRequest) {
+            if (isFallbackAudioHintValid()) {
+                return getBaselineFallbackRoute(mAudioRouteFallbackHint);
+            } else if (mWasOnSpeaker) {
+                return SWITCH_SPEAKER;
+            }
+        }
+
         if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
         } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 1219216..293d03f 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -1,5 +1,6 @@
 package com.android.server.telecom;
 
+import com.android.internal.telephony.TelephonyProperties;
 import com.android.server.telecom.components.ErrorDialogActivity;
 
 import android.content.Context;
@@ -19,6 +20,8 @@
 import android.telephony.PhoneNumberUtils;
 import android.widget.Toast;
 
+import org.codeaurora.ims.QtiCallConstants;
+
 /**
  * Single point of entry for all outgoing and incoming calls.
  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -100,8 +103,22 @@
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
         String uriString = handle.getSchemeSpecificPart();
+        Bundle clientExtras = null;
 
-        if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
+        if (clientExtras == null) {
+            clientExtras = new Bundle();
+        }
+
+        boolean isSkipSchemaParsing = intent.getBooleanExtra(
+                TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false);
+        Log.d(CallIntentProcessor.class, "isSkipSchemaParsing = " + isSkipSchemaParsing);
+        if (isSkipSchemaParsing) {
+            clientExtras.putBoolean(TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING,
+                    isSkipSchemaParsing);
+            handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, handle.toString(), null);
+        }
+
+        if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) && !isSkipSchemaParsing) {
             handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
                     PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
         }
@@ -109,13 +126,28 @@
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
 
-        Bundle clientExtras = null;
         if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
             clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
         }
-        if (clientExtras == null) {
-            clientExtras = new Bundle();
+        boolean isConferenceUri = intent.getBooleanExtra(
+                TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false);
+        Log.d(CallIntentProcessor.class, "isConferenceUri = "+isConferenceUri);
+        if (isConferenceUri) {
+            clientExtras.putBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, isConferenceUri);
         }
+        boolean isAddParticipant = intent.getBooleanExtra(
+                TelephonyProperties.ADD_PARTICIPANT_KEY, false);
+        Log.d(CallIntentProcessor.class, "isAddparticipant = "+isAddParticipant);
+        if (isAddParticipant) {
+            clientExtras.putBoolean(TelephonyProperties.ADD_PARTICIPANT_KEY, isAddParticipant);
+        }
+
+        boolean isCallPull = intent.getBooleanExtra(TelephonyProperties.EXTRA_IS_CALL_PULL, false);
+        if (isCallPull) {
+            clientExtras.putBoolean(TelephonyProperties.EXTRA_IS_CALL_PULL, isCallPull);
+        }
+
+        Log.i(CallIntentProcessor.class, " processOutgoingCallIntent isCallPull = " + isCallPull);
 
         // Ensure call subject is passed on to the connection service.
         if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
@@ -123,10 +155,20 @@
             clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
         }
 
-        final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+        final int callDomain = intent.getIntExtra(
+                QtiCallConstants.EXTRA_CALL_DOMAIN, QtiCallConstants.DOMAIN_AUTOMATIC);
+        Log.d(CallIntentProcessor.class, "callDomain = " + callDomain);
+        clientExtras.putInt(QtiCallConstants.EXTRA_CALL_DOMAIN, callDomain);
+
+        final int videoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 VideoProfile.STATE_AUDIO_ONLY);
         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
 
+        Log.i(CallIntentProcessor.class, " processOutgoingCallIntent handle = " + handle
+                + ",scheme = " + scheme + ", uriString = " + uriString
+                + ", isSkipSchemaParsing = " + isSkipSchemaParsing
+                + ", isAddParticipant = " + isAddParticipant);
+
         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
 
         boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
@@ -154,6 +196,7 @@
             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
 
             if (!success && call != null) {
+                callsManager.clearPendingMOEmergencyCall();
                 disconnectCallAndShowErrorDialog(context, call, result);
             }
         }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
old mode 100755
new mode 100644
index 80f1da2..56fafe9
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -28,6 +28,7 @@
 import android.os.UserHandle;
 import android.os.PersistableBundle;
 import android.provider.CallLog.Calls;
+import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -218,9 +219,9 @@
         int callFeatures = getCallFeatures(call.getVideoStateHistory(),
                 call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED);
         logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
-                call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
-                creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(),
-                logCallCompletedListener);
+                call.getHandlePresentation(), toPreciseLogType(call, callLogType), callFeatures,
+                accountHandle, creationTime, age, callDataUsage, call.isEmergencyCall(),
+                call.getInitiatingUser(), logCallCompletedListener);
     }
 
     /**
@@ -468,4 +469,43 @@
             return mCurrentCountryIso;
         }
     }
+
+    private int toPreciseLogType(Call call, int callLogType) {
+        final boolean isHighDefAudioCall =
+               (call != null) && call.hasProperty(Connection.PROPERTY_HIGH_DEF_AUDIO);
+        final boolean isWifiCall =
+               (call != null) && call.hasProperty(Connection.PROPERTY_WIFI);
+        Log.d(TAG, "callProperties: " + call.getConnectionProperties()
+                + "isHighDefAudioCall: " + isHighDefAudioCall
+                + "isWifiCall: " + isWifiCall);
+        if(!isHighDefAudioCall && !isWifiCall) {
+            return callLogType;
+        }
+        switch (callLogType) {
+            case Calls.INCOMING_TYPE :
+                if(isWifiCall) {
+                    callLogType = Calls.INCOMING_WIFI_TYPE;
+                } else {
+                    callLogType = Calls.INCOMING_IMS_TYPE;
+                }
+                break;
+            case Calls.OUTGOING_TYPE :
+                if(isWifiCall) {
+                    callLogType = Calls.OUTGOING_WIFI_TYPE;
+                } else {
+                    callLogType = Calls.OUTGOING_IMS_TYPE;
+                }
+                break;
+            case Calls.MISSED_TYPE :
+                if(isWifiCall) {
+                    callLogType = Calls.MISSED_WIFI_TYPE;
+                } else {
+                    callLogType = Calls.MISSED_IMS_TYPE;
+                }
+                break;
+            default:
+                //Normal cs call, no change
+        }
+        return callLogType;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index cd2aa85..14c91ae 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -26,7 +26,10 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.SystemVibrator;
@@ -49,6 +52,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.AsyncEmergencyContactNotifier;
@@ -66,8 +70,12 @@
 import com.android.server.telecom.components.ErrorDialogActivity;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import com.android.server.telecom.ui.ViceNotificationImpl;
+
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -78,6 +86,9 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.codeaurora.ims.QtiCallConstants;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+import org.codeaurora.internal.IExtTelephony;
 /**
  * Singleton.
  *
@@ -108,6 +119,7 @@
         void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
         void onCanAddCallChanged(boolean canAddCall);
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+        void onSessionModifyRequestSent(VideoProfile fromProfile, VideoProfile toProfile);
         void onHoldToneRequested(Call call);
         void onExternalCallChanged(Call call, boolean isExternalCall);
     }
@@ -120,6 +132,9 @@
     private static final int MAXIMUM_DIALING_CALLS = 1;
     private static final int MAXIMUM_OUTGOING_CALLS = 1;
     private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
+    private static final int MAXIMUM_DSDA_LIVE_CALLS = 2;
+    private static final int MAXIMUM_DSDA_HOLD_CALLS = 2;
+    private static final int MAXIMUM_DSDA_TOP_LEVEL_CALLS = 4;
 
     private static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -194,6 +209,7 @@
     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
     private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
     private final Timeouts.Adapter mTimeoutsAdapter;
+    private final ViceNotificationImpl mViceNotificationImpl;
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final NotificationManager mNotificationManager;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
@@ -205,7 +221,33 @@
 
     private TelephonyManager.MultiSimVariants mRadioSimVariants = null;
 
+    private static final int LCH_PLAY_DTMF = 100;
+    private static final int LCH_STOP_DTMF = 101;
+    private static final int PHONE_START_DSDA_INCALL_TONE = 102;
+    private static final int SET_LOCAL_CALL_HOLD = 103;
+    private static final int LCH_DTMF_PERIODICITY = 3000;
+    private static final int LCH_DTMF_PERIOD = 500;
+    private static final String sSupervisoryCallHoldToneConfig =
+            SystemProperties.get("persist.radio.sch_tone", "none");
+    private static final String ACTIVE_SUBSCRIPTION = "active_sub";
+    private static InCallTonePlayer.Factory mPlayerFactory;
+    private String mLchSub = null;
+
+    private InCallTonePlayer mLocalCallReminderTonePlayer = null;
+
     private Runnable mStopTone;
+    private String mActiveSub = null;
+    private DsdaAdapter mDsdaAdapter = null;
+
+    private HashMap<String, Boolean> mLchStatus = new HashMap<String, Boolean>();
+
+    // Two global variables used to handle the Emergency Call when there
+    // is no room available for emergency call. Buffer the Emergency Call
+    // in mPendingMOEmerCall until the Current Active call is disconnected
+    // successfully and place the mPendingMOEmerCall followed by clearing
+    // buffer.
+    private Call mPendingMOEmerCall = null;
+    private Call mDisconnectingCall = null;
 
     /**
      * Initializes the required Telecom components.
@@ -227,6 +269,7 @@
             DefaultDialerManagerAdapter defaultDialerAdapter,
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
+            ViceNotifier viceNotifier,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             InterruptionFilterProxy interruptionFilterProxy) {
         mContext = context;
@@ -284,6 +327,7 @@
                 playerFactory, mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
 
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
+        mPlayerFactory = playerFactory;
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
@@ -291,6 +335,7 @@
         mConnectionServiceRepository =
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
+        mViceNotificationImpl = viceNotifier.create(mContext, this);
 
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
@@ -301,6 +346,7 @@
         mListeners.add(missedCallNotifier);
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
+        mListeners.add(mViceNotificationImpl);
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -310,6 +356,10 @@
         }
     }
 
+    ViceNotificationImpl getViceNotificationImpl() {
+        return mViceNotificationImpl;
+    }
+
     public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
         if (mRespondViaSmsManager != null) {
             mListeners.remove(mRespondViaSmsManager);
@@ -386,7 +436,7 @@
         }
 
         if (result.shouldAllowCall) {
-            if (hasMaximumRingingCalls()) {
+            if (hasMaximumRingingCalls(incomingCall.getTargetPhoneAccount().getId())) {
                 if (shouldSilenceInsteadOfReject(incomingCall)) {
                     incomingCall.silence();
                 } else {
@@ -398,8 +448,14 @@
                 Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
                         "dialing calls.");
                 rejectCallAndLog(incomingCall);
+            } else if (!isIncomingVideoCallAllowed(incomingCall, mContext)) {
+                Toast.makeText(mContext, mContext.getResources().
+                        getString(R.string.incoming_call_failed_low_battery), Toast.LENGTH_LONG).
+                        show();
+                rejectCallAndLog(incomingCall);
             } else {
                 addCall(incomingCall);
+                setActiveSubscription(incomingCall.getTargetPhoneAccount().getId());
             }
         } else {
             if (result.shouldReject) {
@@ -421,6 +477,36 @@
     }
 
     /**
+     * Determines if the incoming video call is allowed or not
+     *
+     * @param Call The incoming call.
+     * @return {@code false} if incoming video call is not allowed.
+     */
+    private static boolean isIncomingVideoCallAllowed(Call call, Context context) {
+        Bundle extras = call.getExtras();
+        if (extras == null || (!isIncomingVideoCall(call)) ||
+                QtiImsExtUtils.allowVideoCallsInLowBattery(context)) {
+            Log.w(TAG, "isIncomingVideoCallAllowed: null Extras or not an incoming video call " +
+                    "or allow video calls in low battery");
+            return true;
+        }
+
+        final boolean isLowBattery = extras.getBoolean(QtiCallConstants.LOW_BATTERY_EXTRA_KEY,
+                false);
+        Log.d(TAG, "isIncomingVideoCallAllowed: lowbattery = " + isLowBattery);
+        return !isLowBattery;
+    }
+
+    private static boolean isIncomingVideoCall(Call call) {
+        if (VideoProfile.isAudioOnly(call.getVideoState())) {
+            return false;
+        }
+
+        final int state = call.getState();
+        return (state == CallState.RINGING);
+    }
+
+    /**
      * Whether allow (silence rather than reject) the incoming call if it has a different source
      * (connection service) from the existing ringing call when reaching maximum ringing calls.
      */
@@ -607,6 +693,22 @@
         }
     }
 
+    /**
+     * Handles session modification requests sent
+     *
+     * @param fromProfile The video properties prior to the request.
+     * @param toProfile The video properties with the requested changes made.
+     */
+    @Override
+    public void onSessionModifyRequestSent(VideoProfile fromProfile, VideoProfile toProfile) {
+        Log.v(TAG, "onSessionModifyRequestSent : fromProfile = " + fromProfile +
+                " toProfile = " + toProfile);
+
+        for (CallsManagerListener listener : mListeners) {
+            listener.onSessionModifyRequestSent(fromProfile, toProfile);
+        }
+    }
+
     public Collection<Call> getCalls() {
         return Collections.unmodifiableCollection(mCalls);
     }
@@ -835,6 +937,12 @@
                     false /* forceAttachToExistingConnection */,
                     false /* isConference */
             );
+            if ((extras != null) &&
+                    extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)) {
+                //Reset PostDialDigits with empty string for ConfURI call.
+                call.setPostDialDigits("");
+            }
+
             call.initAnalytics();
 
             call.setInitiatingUser(initiatingUser);
@@ -876,7 +984,30 @@
             call.setVideoState(videoState);
         }
 
-        List<PhoneAccountHandle> accounts = constructPossiblePhoneAccounts(handle, initiatingUser);
+        boolean isAddParticipant = ((extras != null) && (extras.getBoolean(
+                TelephonyProperties.ADD_PARTICIPANT_KEY, false)));
+        boolean isSkipSchemaOrConfUri = ((extras != null) && (extras.getBoolean(
+                TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) ||
+                extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)));
+
+        if (isAddParticipant) {
+            String number = handle.getSchemeSpecificPart();
+            if (!isSkipSchemaOrConfUri) {
+                number = PhoneNumberUtils.stripSeparators(number);
+            }
+            addParticipant(number);
+            mInCallController.bringToForeground(false);
+            return null;
+        }
+
+        // Force tel scheme for ims conf uri/skip schema calls to avoid selection of sip accounts
+        String scheme = (isSkipSchemaOrConfUri? PhoneAccount.SCHEME_TEL: handle.getScheme());
+
+        Log.d(this, "startOutgoingCall :: isAddParticipant=" + isAddParticipant
+                + " isSkipSchemaOrConfUri=" + isSkipSchemaOrConfUri + " scheme=" + scheme);
+
+        List<PhoneAccountHandle> accounts =
+                constructPossiblePhoneAccounts(handle, initiatingUser, scheme);
         Log.v(this, "startOutgoingCall found accounts = " + accounts);
 
         // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
@@ -893,7 +1024,7 @@
             // handle and verify it can be used.
             if(accounts.size() > 1) {
                 PhoneAccountHandle defaultPhoneAccountHandle =
-                        mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme(),
+                        mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(scheme,
                                 initiatingUser);
                 if (defaultPhoneAccountHandle != null &&
                         accounts.contains(defaultPhoneAccountHandle)) {
@@ -903,7 +1034,6 @@
                 // Use the only PhoneAccount that is available
                 phoneAccountHandle = accounts.get(0);
             }
-
         }
 
         call.setTargetPhoneAccount(phoneAccountHandle);
@@ -946,7 +1076,10 @@
         // Do not add the call if it is a potential MMI code.
         if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
             call.addListener(this);
-        } else if (!mCalls.contains(call)) {
+        // If call is Emergency type and marked it as Pending, call would not be added
+        // in mCalls here. It will be handled when the current active call (mDisconnectingCall)
+        // is disconnected successfully.
+        } else if (!mCalls.contains(call) && mPendingMOEmerCall == null) {
             // We check if mCalls already contains the call because we could potentially be reusing
             // a call which was previously added (See {@link #reuseOutgoingCall}).
             addCall(call);
@@ -1012,9 +1145,16 @@
                 com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
 
         if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
+            if (!call.isEmergencyCall()) {
+                updateLchStatus(call.getTargetPhoneAccount().getId());
+            }
+            //Block to initiate Emregency call now as the current active call
+            //is not yet disconnected.
+            if (mPendingMOEmerCall == null) {
             // If the account has been set, proceed to place the outgoing call.
             // Otherwise the connection will be initiated when the account is set by the user.
-            call.startCreateConnection(mPhoneAccountRegistrar);
+                call.startCreateConnection(mPhoneAccountRegistrar);
+            }
         } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
                 requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false,
                 call.getInitiatingUser()).isEmpty()) {
@@ -1026,6 +1166,23 @@
     }
 
     /**
+     * Attempts to add participant in a call.
+     *
+     * @param number number to connect the call with.
+     */
+    private void addParticipant(String number) {
+        Log.i(this, "addParticipant number ="+number);
+        if (getForegroundCall() == null) {
+            // don't do anything if the call no longer exists
+            Log.i(this, "Canceling unknown call.");
+            return;
+        } else {
+            getForegroundCall().addParticipantWithConference(number);
+        }
+    }
+
+
+    /**
      * Attempts to start a conference call for the specified call.
      *
      * @param call The call to conference.
@@ -1049,20 +1206,21 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
-            Call foregroundCall = getForegroundCall();
+            Call activeCall = getFirstCallWithState(call.getTargetPhoneAccount()
+                    .getId(), CallState.ACTIVE, CallState.DIALING);
             // If the foreground call is not the ringing call and it is currently isActive() or
             // STATE_DIALING, put it on hold before answering the call.
-            if (foregroundCall != null && foregroundCall != call &&
-                    (foregroundCall.isActive() ||
-                     foregroundCall.getState() == CallState.DIALING ||
-                     foregroundCall.getState() == CallState.PULLING)) {
-                if (0 == (foregroundCall.getConnectionCapabilities()
+            if (activeCall != null && activeCall != call &&
+                    (activeCall.isActive() ||
+                     activeCall.getState() == CallState.DIALING ||
+                     activeCall.getState() == CallState.PULLING )) {
+                if (0 == (activeCall.getConnectionCapabilities()
                         & Connection.CAPABILITY_HOLD)) {
                     // This call does not support hold.  If it is from a different connection
                     // service, then disconnect it, otherwise allow the connection service to
                     // figure out the right states.
-                    if (foregroundCall.getConnectionService() != call.getConnectionService()) {
-                        foregroundCall.disconnect();
+                    if (activeCall.getConnectionService() != call.getConnectionService()) {
+                        activeCall.disconnect();
                     }
                 } else {
                     Call heldCall = getHeldCall();
@@ -1073,8 +1231,8 @@
                     }
 
                     Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
-                            foregroundCall, call);
-                    foregroundCall.hold();
+                            activeCall, call);
+                    activeCall.hold();
                 }
                 // TODO: Wait until we get confirmation of the active call being
                 // on-hold before answering the new call.
@@ -1084,7 +1242,7 @@
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallAnswered(call);
             }
-
+            updateLchStatus(call.getTargetPhoneAccount().getId());
             // We do not update the UI until we get confirmation of the answer() through
             // {@link #markCallAsActive}.
             call.answer(videoState);
@@ -1146,6 +1304,7 @@
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
             }
+            setActiveSubscription(getConversationSub());
             call.reject(rejectWithMessage, textMessage);
         }
     }
@@ -1246,8 +1405,13 @@
             boolean otherCallHeld = false;
             Log.d(this, "unholding call: (%s)", call);
             for (Call c : mCalls) {
+                PhoneAccountHandle ph = call.getTargetPhoneAccount();
+                PhoneAccountHandle ph1 = c.getTargetPhoneAccount();
                 // Only attempt to hold parent calls and not the individual children.
-                if (c != null && c.isAlive() && c != call && c.getParentCall() == null) {
+                // if 'c' is not for same subscription as call, then don't disturb 'c'
+                if (c != null && c.isAlive() && c != call && c.getParentCall() == null
+                        && (ph != null && ph1 != null &&
+                        isSamePhAccIdOrSipId(ph.getId(), ph1.getId()))) {
                     otherCallHeld = true;
                     Log.event(c, Log.Events.SWAP);
                     c.hold();
@@ -1265,20 +1429,44 @@
         if (source != Call.SOURCE_CONNECTION_SERVICE) {
             return;
         }
+
         handleCallTechnologyChange(c);
         handleChildAddressChange(c);
+
+        if (extras != null) {
+            boolean isNeedReset = extras.getBoolean("isNeedReset", false);
+            handleCdmaConnectionTimeReset(c, isNeedReset);
+        }
+        updateCanAddCall();
+    }
+
+    void handleCdmaConnectionTimeReset(Call call, boolean isNeedReset) {
+        if (isNeedReset && call != null) {
+            call.setConnectTimeMillis(System.currentTimeMillis());
+            if (mCalls.contains(call)) {
+                for (CallsManagerListener listener : mListeners) {
+                    listener.onCallStateChanged(call, CallState.ACTIVE, CallState.ACTIVE);
+                }
+            }
+            call.removeExtras(Call.SOURCE_INCALL_SERVICE,
+                    new ArrayList<String>(Arrays.asList("isNeedReset")));
+        }
         updateCanAddCall();
     }
 
     // Construct the list of possible PhoneAccounts that the outgoing call can use based on the
     // active calls in CallsManager. If any of the active calls are on a SIM based PhoneAccount,
     // then include only that SIM based PhoneAccount and any non-SIM PhoneAccounts, such as SIP.
-    private List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user) {
+    private List<PhoneAccountHandle> constructPossiblePhoneAccounts(
+            Uri handle, UserHandle user, String scheme) {
         if (handle == null) {
             return Collections.emptyList();
         }
+        if (scheme == null) {
+            scheme = handle.getScheme();
+        }
         List<PhoneAccountHandle> allAccounts =
-                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user);
+                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(scheme, false, user);
         // First check the Radio SIM Technology
         if(mRadioSimVariants == null) {
             TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
@@ -1344,6 +1532,15 @@
             call.setViaNumber(viaNumber);
         }
     }
+    /**
+     *  Returns true if the ids are same or one of the ids is sip id.
+     */
+    private boolean isSamePhAccIdOrSipId(String id1, String id2) {
+        boolean ret = ((id1 != null && id2 != null) &&
+                (id1.equals(id2) || id1.contains("@") || id2.contains("@")));
+        Log.d(this, "isSamePhAccIdOrSipId: id1 = " + id1 + " id2 = " + id2 + " ret = " + ret);
+        return ret;
+    }
 
     /** Called by the in-call UI to change the mute state. */
     void mute(boolean shouldMute) {
@@ -1355,6 +1552,13 @@
       * speaker phone.
       */
     void setAudioRoute(int route) {
+        Call call = getDialingCall();
+        if (call != null && call.getStartWithSpeakerphoneOn()) {
+            /* There is a change in audio routing preferance for the call.
+             * So, honour the new audio routing preferance.
+             */
+            call.setStartWithSpeakerphoneOn(false);
+        }
         mCallAudioManager.setAudioRoute(route);
     }
 
@@ -1376,8 +1580,19 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Attempted to add account to unknown call %s", call);
         } else {
+            Log.i(this, "phoneAccountSelected , id = %s", account.getId());
+            updateLchStatus(account.getId());
             call.setTargetPhoneAccount(account);
 
+            // Set video state after selection of phone account.
+            Bundle extras = call.getIntentExtras();
+            if (extras != null) {
+                int videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                        VideoProfile.STATE_AUDIO_ONLY);
+                Log.d(this, "phoneAccountSelected , setVideoState : " + videoState);
+                call.setVideoState(videoState);
+            }
+
             if (!call.isNewOutgoingCallIntentBroadcastDone()) {
                 return;
             }
@@ -1413,7 +1628,9 @@
 
     void markCallAsDialing(Call call) {
         setCallState(call, CallState.DIALING, "dialing set explicitly");
+        maybeMoveToEarpiece(call);
         maybeMoveToSpeakerPhone(call);
+        setActiveSubscription(call.getTargetPhoneAccount().getId());
     }
 
     void markCallAsPulling(Call call) {
@@ -1438,7 +1655,44 @@
      */
     void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
         call.setDisconnectCause(disconnectCause);
+        int prevState = call.getState();
         setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
+        String lchSub = getLchSub();
+        String subId = (call.getTargetPhoneAccount() == null) ? null :
+                call.getTargetPhoneAccount().getId();
+        String subInConversation = getConversationSub();
+        if (subId != null && subId.equals(mActiveSub) &&
+                getFirstCallWithState(subId, CallState.RINGING) == null  &&
+                (subInConversation != null && !subInConversation.equals(mActiveSub))) {
+            Log.d(this,"Set active sub to conversation sub");
+            switchToOtherActiveSub(subInConversation);
+        } else if ((subInConversation == null) && (lchSub != null) &&
+                ((prevState == CallState.CONNECTING) || (prevState ==
+                CallState.SELECT_PHONE_ACCOUNT)) &&
+                (call.getState() == CallState.DISCONNECTED)) {
+            Log.d(this,"remove sub with call from LCH");
+            updateLchStatus(lchSub);
+            setActiveSubscription(lchSub);
+            manageDsdaInCallTones(false);
+        }
+        boolean isLchEnabled = (mLchStatus.get(subId) == null) ? false : mLchStatus.get(subId);
+        if ((subId != null) && isLchEnabled) {
+            Call activecall = getFirstCallWithState(subId, CallState.RINGING, CallState.DIALING,
+                    CallState.ACTIVE, CallState.ON_HOLD);
+            Log.d(this,"activecall: " + activecall);
+            if (activecall == null) {
+                mLchStatus.put(subId, false);
+                manageDsdaInCallTones(false);
+            }
+        }
+        // Emergency MO call is still pending and current active call is
+        // disconnected succesfully. So initiating pending Emergency call
+        // now and clearing both pending and Disconnectcalls.
+        if (mPendingMOEmerCall != null && mDisconnectingCall == call) {
+            addCall(mPendingMOEmerCall);
+            mPendingMOEmerCall.startCreateConnection(mPhoneAccountRegistrar);
+            clearPendingMOEmergencyCall();
+        }
     }
 
     /**
@@ -1446,13 +1700,22 @@
      */
     void markCallAsRemoved(Call call) {
         removeCall(call);
+        if (!hasAnyCalls()) {
+            updateLchStatus(null);
+            setActiveSubscription(null);
+            manageDsdaInCallTones(false);
+        }
         Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        boolean isChildCall = call.isChildCall();
         if (mLocallyDisconnectingCalls.contains(call)) {
+            Log.v(this, "markCallAsRemoved: isChildCall = "
+                + isChildCall + "call -> %s", call);
             mLocallyDisconnectingCalls.remove(call);
-            if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+            if (!isChildCall && foregroundCall != null
+                    && foregroundCall.getState() == CallState.ON_HOLD) {
                 foregroundCall.unhold();
             }
-        } else if (foregroundCall != null &&
+        } else if (!isChildCall && foregroundCall != null &&
                 !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD)  &&
                 foregroundCall.getState() == CallState.ON_HOLD) {
 
@@ -1505,6 +1768,10 @@
         return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
     }
 
+    boolean hasActiveOrHoldingCall(String sub) {
+        return (getFirstCallWithState(sub, CallState.ACTIVE, CallState.ON_HOLD) != null);
+    }
+
     boolean hasRingingCall() {
         return getFirstCallWithState(CallState.RINGING) != null;
     }
@@ -1573,7 +1840,12 @@
             // we could put InCallServices into a state where they are showing two calls but
             // also support add-call. Technically it's right, but overall looks better (UI-wise)
             // and acts better if we wait until the call is removed.
-            if (count >= MAXIMUM_TOP_LEVEL_CALLS) {
+            if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                    == TelephonyManager.MultiSimVariants.DSDA) {
+                if (count >= MAXIMUM_DSDA_TOP_LEVEL_CALLS) {
+                    return false;
+                }
+            } else if (count >= MAXIMUM_TOP_LEVEL_CALLS) {
                 return false;
             }
         }
@@ -1618,7 +1890,7 @@
 
     @VisibleForTesting
     public Call getFirstCallWithState(int... states) {
-        return getFirstCallWithState(null, states);
+        return getFirstCallWithState((Call) null, states);
     }
 
     @VisibleForTesting
@@ -1663,6 +1935,43 @@
         return null;
     }
 
+    /**
+     * Returns the first call that it finds with the given states for given subscription.
+     * the states are treated as having priority order so that any call with the first
+     * state will be returned before any call with states listed later in the parameter list.
+     *
+     * @param subId check calls only on this subscription
+     * @param callToSkip Call that this method should skip while searching
+     */
+    Call getFirstCallWithState(String subId, Call callToSkip, int... states) {
+        for (int currentState : states) {
+            // check the foreground first
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == currentState
+                    && foregroundCall.getTargetPhoneAccount() != null
+                    && isSamePhAccIdOrSipId(foregroundCall.getTargetPhoneAccount().getId(), subId)) {
+                return foregroundCall;
+            }
+
+            for (Call call : mCalls) {
+                if (Objects.equals(callToSkip, call)) {
+                    continue;
+                }
+
+                // Only operate on top-level calls
+                if (call.getParentCall() != null) {
+                    continue;
+                }
+
+                if (currentState == call.getState() && call.getTargetPhoneAccount() != null
+                        && isSamePhAccIdOrSipId(call.getTargetPhoneAccount().getId(), subId)) {
+                    return call;
+                }
+            }
+        }
+        return null;
+    }
+
     Call createConferenceCall(
             String callId,
             PhoneAccountHandle phoneAccount,
@@ -1863,6 +2172,7 @@
             }
             Trace.endSection();
         }
+        manageDsdaInCallTones(false);
     }
 
     private void updateCanAddCall() {
@@ -1926,18 +2236,44 @@
         return count;
     }
 
+    private int getNumCallsWithState(String subId, int... states) {
+        int count = 0;
+        for (int state : states) {
+            for (Call call : mCalls) {
+                if (call.getParentCall() == null && call.getState() == state
+                        && call.getTargetPhoneAccount() != null
+                        && isSamePhAccIdOrSipId(call.getTargetPhoneAccount().getId(), subId)) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
     private boolean hasMaximumLiveCalls() {
         return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES);
     }
 
+    private boolean hasMaximumLiveCalls(String subId) {
+        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(subId, LIVE_CALL_STATES);
+    }
+
     private boolean hasMaximumHoldingCalls() {
         return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD);
     }
 
+    private boolean hasMaximumHoldingCalls(String subId) {
+        return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(subId, CallState.ON_HOLD);
+    }
+
     private boolean hasMaximumRingingCalls() {
         return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING);
     }
 
+    private boolean hasMaximumRingingCalls(String subId) {
+        return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(subId, CallState.RINGING);
+    }
+
     private boolean hasMaximumOutgoingCalls() {
         return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES);
     }
@@ -1946,7 +2282,34 @@
         return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING, CallState.PULLING);
     }
 
+    /**
+     * Returns true if there is an Emergency Call in Call list.
+     */
+    private boolean IsEmergencyCallInProgress() {
+        for (Call call : mCalls) {
+            if (call.isEmergencyCall()) {
+                // We never support add call if one of the calls is an emergency call.
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
+        if (isEmergency && IsEmergencyCallInProgress()) {
+            Log.i(this, "emergency call is progress so no room for new E Call");
+            return false;
+        }
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                == TelephonyManager.MultiSimVariants.DSDA) {
+            return makeRoomForOutgoingCallForDsda(call, isEmergency);
+        }
+        // Reject If there is any Incoming Call while initiating an
+        // an Emergency Call.
+        if (isEmergency && hasMaximumRingingCalls()) {
+            Call rinigingCall = getRingingCall();
+            rinigingCall.reject(false, null);
+        }
         if (hasMaximumLiveCalls()) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
             // have to change.
@@ -1970,6 +2333,12 @@
                     call.getAnalytics().setCallIsAdditional(true);
                     outgoingCall.getAnalytics().setCallIsInterrupted(true);
                     outgoingCall.disconnect();
+                    // buffer this call in to mPendingMOEmerCall and do not initiate this call
+                    // until the current outgoing call mDisconnectingCall is disconnected
+                    // successfully. Emergency Call would be initiated upon receiving the
+                    // Disconnection response from lower layers.
+                    mDisconnectingCall = outgoingCall;
+                    mPendingMOEmerCall = call;
                     return true;
                 }
                 if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
@@ -1991,6 +2360,12 @@
                     call.getAnalytics().setCallIsAdditional(true);
                     liveCall.getAnalytics().setCallIsInterrupted(true);
                     liveCall.disconnect();
+                    // buffer this call in to mPendingMOEmerCall and do not initiate this call
+                    // until the current live call "mDisconnectingCall" is disconnected
+                    // successfully. Emergency Call would be initiated upon receiving the
+                    // Disconnection response from lower layers.
+                    mDisconnectingCall = liveCall;
+                    mPendingMOEmerCall = call;
                     return true;
                 }
                 return false;  // No more room!
@@ -2047,6 +2422,44 @@
         return true;
     }
 
+    private boolean makeRoomForOutgoingCallForDsda(Call call, boolean isEmergency) {
+        if (isEmergency || (call.getState() == CallState.SELECT_PHONE_ACCOUNT)) {
+            return true;
+        }
+
+        PhoneAccountHandle phAcc = call.getTargetPhoneAccount();
+        if (phAcc == null) {
+            if (getNumCallsWithState(LIVE_CALL_STATES) == MAXIMUM_DSDA_LIVE_CALLS
+                    && getNumCallsWithState(CallState.ON_HOLD) == MAXIMUM_DSDA_HOLD_CALLS) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+        if (hasMaximumLiveCalls(phAcc.getId())) {
+            // NOTE: If the amount of live calls changes beyond 1, this logic will probably
+            // have to change.
+            Call liveCall = getFirstCallWithState(phAcc.getId(), call, LIVE_CALL_STATES);
+
+            if (hasMaximumHoldingCalls(phAcc.getId())) {
+                // There is no more room for any more calls, unless it's an emergency.
+                return false;  // No more room!
+            }
+            if (Objects.equals(liveCall.getTargetPhoneAccount(), call.getTargetPhoneAccount())) {
+                return true;
+            }
+            // Try to hold the live call before attempting the new outgoing call.
+            if (liveCall.can(Connection.CAPABILITY_HOLD)) {
+                liveCall.hold();
+                return true;
+            }
+
+            // The live call cannot be held so we're out of luck here.  There's no room.
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Given a call, find the first non-null phone account handle of its children.
      *
@@ -2073,6 +2486,13 @@
         }
     }
 
+    private void maybeMoveToEarpiece(Call call) {
+        if (!call.getStartWithSpeakerphoneOn() && !mWiredHeadsetManager.isPluggedIn() &&
+                !mBluetoothManager.isBluetoothAvailable()) {
+            setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+        }
+    }
+
     /**
      * Creates a new call for an existing connection.
      *
@@ -2106,6 +2526,7 @@
 
         setCallState(call, Call.getStateFromConnectionState(connection.getState()),
                 "existing connection");
+        call.setVideoState(connection.getVideoState());
         call.setConnectionCapabilities(connection.getConnectionCapabilities());
         call.setConnectionProperties(connection.getConnectionProperties());
         call.setCallerDisplayName(connection.getCallerDisplayName(),
@@ -2265,4 +2686,339 @@
 
       call.setIntentExtras(extras);
     }
+
+    private final Handler mLchHandler = new LchHandler();
+    private final class LchHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PHONE_START_DSDA_INCALL_TONE:
+                    Log.d(this, "Start DSDA incall tones...");
+                    startDsdaInCallTones();
+                    break;
+                case LCH_PLAY_DTMF:
+                    playLchDtmf();
+                    break;
+                case LCH_STOP_DTMF:
+                    stopLchDtmf();
+                    break;
+                case SET_LOCAL_CALL_HOLD:
+                    updateLchStatusToRil((String) msg.obj);
+                    break;
+            }
+        }
+    }
+
+    void switchToOtherActiveSub(String subId) {
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                != TelephonyManager.MultiSimVariants.DSDA) {
+            return;
+        }
+        Log.d(this, "switchToOtherActiveSub sub:" + subId);
+        setActiveSubscription(subId);
+        updateLchStatus(subId);
+        manageDsdaInCallTones(true);
+    }
+
+    String getActiveSubscription() {
+        return mActiveSub;
+    }
+
+    synchronized void setActiveSubscription(String subId) {
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                != TelephonyManager.MultiSimVariants.DSDA) {
+            return;
+        }
+        Log.d(this, "setActiveSubscription = " + subId);
+        if (subId == null) {
+            mActiveSub = null;
+            return;
+        }
+        if (subId.equals(mActiveSub)) {
+            Log.d(this, "setActiveSubscription not changed " + subId + " mActiveSub:" + mActiveSub);
+            return;
+        }
+        mActiveSub = subId;
+        Log.d(this, "setActiveSubscription changed " + mActiveSub);
+        for (Call call : mCalls) {
+            PhoneAccountHandle ph = call.getTargetPhoneAccount();
+            if (ph != null && subId.equals(ph.getId())) {
+                Bundle extras = new Bundle();
+                extras.putBoolean(ACTIVE_SUBSCRIPTION, true);
+                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+            } else {
+                Bundle extras = new Bundle();
+                extras.putBoolean(ACTIVE_SUBSCRIPTION, false);
+                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+            }
+        }
+    }
+
+    void manageDsdaInCallTones(boolean isSubSwitch) {
+        Log.d(this, " entered manageDsdaInCallTones ");
+
+        // If there is no background active subscription available, stop playing the tones.
+        // Do not start/stop LCH/SCH tones when phone is in RINGING state.
+        if (getLchSub() != null && !hasRingingCall()) {
+            //If sub switch happens re-start the tones with a delay of 100msec.
+            if (isSubSwitch) {
+                Log.d(this, " manageDsdaInCallTones: re-start playing tones, lch sub = "
+                        + getLchSub());
+                reStartDsdaInCallTones();
+            } else {
+                Log.d(this, " entered manageDsdaInCallTones ");
+                startDsdaInCallTones();
+            }
+        } else if (getLchSub() == null) {
+            // if there is no sub in Lch state, then stop playing the tones
+            stopMSimInCallTones();
+        }
+    }
+
+    private void reStartDsdaInCallTones() {
+        Log.d(this, " reStartDsdaInCallTones");
+        stopMSimInCallTones();
+        /* Remove any pending PHONE_START_DSDA_INCALL_TONE messages from queue */
+        mLchHandler.removeMessages(PHONE_START_DSDA_INCALL_TONE);
+        Message message = Message.obtain(mLchHandler, PHONE_START_DSDA_INCALL_TONE);
+        mLchHandler.sendMessageDelayed(message, 100);
+    }
+
+    /**
+     * Returns the first call that it finds with the given states for given subscription.
+     * The states are treated as having priority order so that any call with the first
+     * state will be returned before any call with states listed later in the parameter list.
+     */
+    Call getFirstCallWithState(String sub, int... states) {
+        for (int currentState : states) {
+            // check the foreground first
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == currentState
+                    && (foregroundCall.getTargetPhoneAccount() != null)
+                    && isSamePhAccIdOrSipId(foregroundCall.getTargetPhoneAccount().getId(),
+                    sub)) {
+                return foregroundCall;
+            }
+
+            for (Call call : mCalls) {
+                if ((currentState == call.getState()) &&
+                        (call.getTargetPhoneAccount() != null) &&
+                        (isSamePhAccIdOrSipId(call.getTargetPhoneAccount().getId(), sub))) {
+                    return call;
+                }
+            }
+        }
+        return null;
+    }
+    /**
+     * Check whether any other sub is in active state other than
+     * provided subscription, if yes return the other active sub.
+     * @return subscription which is active.
+     */
+    private String getOtherActiveSub(String subscription) {
+        String otherSub = null;;
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar()
+                .getSimPhoneAccountsOfCurrentUser()) {
+            String sub = ph.getId();
+            if (!sub.equals(subscription) && getFirstCallWithState(sub, CallState.CONNECTING,
+                    CallState.DIALING, CallState.ACTIVE) != null) {
+                otherSub = sub;
+            }
+        }
+        return otherSub;
+    }
+
+    private String getConversationSub() {
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar()
+                .getSimPhoneAccountsOfCurrentUser()) {
+            String sub = ph.getId();
+            if ((mLchStatus.get(sub) == null || mLchStatus.get(sub) == false)
+                    && getFirstCallWithState(sub, CallState.CONNECTING,
+                    CallState.DIALING, CallState.ACTIVE) != null) {
+                return sub;
+            }
+        }
+        return null;
+    }
+
+    private String getLchSub() {
+        Iterator<String> keySetIterator = mLchStatus.keySet().iterator();
+        while(keySetIterator.hasNext()){
+            String sub = keySetIterator.next();
+            if (mLchStatus.get(sub)!= null && mLchStatus.get(sub)) {
+                return sub;
+            }
+        }
+        return null;
+    }
+
+    private void playLchDtmf() {
+        if (mLchSub != null || mLchHandler.hasMessages(LCH_PLAY_DTMF)) {
+            // Ignore any redundant requests to start playing tones
+            return;
+        }
+        mLchSub = getLchSub();
+        Log.d(this, " playLchDtmf... lch sub " + mLchSub);
+        if (mLchSub == null) return;
+        removeAnyPendingDtmfMsgs();
+        char c = mContext.getResources()
+                .getString(R.string.lch_dtmf_key).charAt(0);
+        Call call = getNonRingingLiveCall(mLchSub);
+        if (call == null) {
+            mLchSub = null;
+            return;
+        }
+        call.playDtmfTone(c);
+        // Keep playing LCH DTMF tone to remote party on LCH call, with periodicity
+        // "LCH_DTMF_PERIODICITY" until call moves out of LCH.
+        mLchHandler.sendMessageDelayed(Message.obtain(mLchHandler, LCH_PLAY_DTMF),
+                LCH_DTMF_PERIODICITY);
+        mLchHandler.sendMessageDelayed(Message.obtain(mLchHandler, LCH_STOP_DTMF), LCH_DTMF_PERIOD);
+    }
+
+    private Call getNonRingingLiveCall(String subId) {
+        return getFirstCallWithState(subId, CallState.DIALING,
+                CallState.ACTIVE, CallState.ON_HOLD);
+    }
+
+    private void stopLchDtmf() {
+        if (mLchSub != null) {
+            // Ignore any redundant requests to stop playing tones
+            Call call = getNonRingingLiveCall(mLchSub);
+            Log.d(this, " stopLchDtmf... call: " + call + " mLchSub:" + mLchSub);
+            if (call == null) {
+                mLchSub = null;
+                return;
+            }
+            call.stopDtmfTone();
+        }
+        mLchSub = null;
+    }
+
+    private void startDsdaInCallTones() {
+        if (mLocalCallReminderTonePlayer == null) {
+            Log.d(this, " Play local call hold reminder tone ");
+            mLocalCallReminderTonePlayer =
+                    mPlayerFactory.createPlayer(InCallTonePlayer.TONE_HOLD_RECALL);
+            mLocalCallReminderTonePlayer.start();
+        }
+        if (sSupervisoryCallHoldToneConfig.equals("dtmf")) {
+            Log.d(this, " startDsdaInCallTones: Supervisory call hold tone over dtmf ");
+            playLchDtmf();
+        }
+    }
+
+    private void removeAnyPendingDtmfMsgs() {
+        mLchHandler.removeMessages(LCH_PLAY_DTMF);
+        mLchHandler.removeMessages(LCH_STOP_DTMF);
+    }
+
+    protected void stopMSimInCallTones() {
+        if (mLocalCallReminderTonePlayer != null) {
+            Log.d(this, " stopMSimInCallTones: local call hold reminder tone ");
+            mLocalCallReminderTonePlayer.stopTone();
+            mLocalCallReminderTonePlayer = null;
+        }
+        if (sSupervisoryCallHoldToneConfig.equals("dtmf")) {
+            Log.d(this, " stopMSimInCallTones: stop SCH Dtmf call hold tone ");
+            stopLchDtmf();
+            /* Remove any previous dtmf nssages from queue */
+            removeAnyPendingDtmfMsgs();
+        }
+    }
+
+    private void updateLchStatus (String subId) {
+        mLchHandler.obtainMessage(SET_LOCAL_CALL_HOLD, subId).sendToTarget();
+    }
+
+    /**
+     * Update the local call hold state for all subscriptions
+     * 1 -- if call on local hold, 0 -- if call is not on local hold
+     *
+     * @param subInConversation is the sub user is currently active in subsription.
+     * so if this sub is in LCH, then bring that sub out of LCH.
+     */
+    private void updateLchStatusToRil(String subInConversation) {
+        String removeFromLch = null;
+        Log.d(this, "updateLchStatusToRil subInConversation: " + subInConversation);
+        if ((subInConversation != null && (subInConversation.contains("sip")
+                || subInConversation.contains("@")))
+                || (TelephonyManager.getDefault()
+                .getMultiSimConfiguration() != TelephonyManager.MultiSimVariants.DSDA)) {
+            return;
+        }
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar()
+                .getSimPhoneAccountsOfCurrentUser()) {
+            String sub = ph.getId();
+            if (sub != null && sub.contains("sip")) {
+                Log.d(this, "update lch. Skipping account: " + sub);
+                continue;
+            }
+            boolean lchState = false;
+            Boolean isLchEnabled = mLchStatus.get(sub);
+            isLchEnabled = (isLchEnabled == null) ? false : isLchEnabled;
+            if (subInConversation != null && hasActiveOrHoldingCall(sub) &&
+                    !sub.equals(subInConversation)) {
+                // if sub is not conversation  sub and if it has an active
+                // voice call then update lchStatus as Active
+                lchState = true;
+            }
+
+            // Update state only if the new state is different
+            if (lchState != isLchEnabled) {
+                Call call = getNonRingingLiveCall(sub);
+                Log.d(this, " setLocal Call Hold to  = " + lchState + " sub:" + sub);
+
+                if (lchState) {
+                    mLchStatus.put(sub, true);
+                    setLocalCallHold(sub, true);
+                } else {
+                    removeFromLch = sub;
+                    mLchStatus.put(sub, false);
+                }
+            }
+        }
+        if (removeFromLch != null) {
+            // Ensure to send LCH disable request at last, to make sure that during switch
+            // subscription, both subscriptions not to be in active(non-LCH) at any moment.
+            setLocalCallHold(removeFromLch, false);
+        }
+    }
+
+    void setDsdaAdapter() {
+        if (mDsdaAdapter != null) {
+            return;
+        }
+        mDsdaAdapter = new DsdaAdapter(this);
+        IExtTelephony mExtTelephony = IExtTelephony.Stub.asInterface(ServiceManager
+            .getService("extphone"));
+        try {
+            Log.d(this, "setDsdaAdapter");
+            mExtTelephony.setDsdaAdapter(mDsdaAdapter);
+        } catch (NullPointerException ex) {
+            Log.d(this, "setDsdaAdapter" + ex);
+        } catch (RemoteException ex) {
+            Log.d(this, "setDsdaAdapter" + ex);
+        }
+    }
+
+    private void setLocalCallHold(String subscriptionId, boolean enable) {
+        IExtTelephony mExtTelephony = IExtTelephony.Stub.asInterface(ServiceManager
+            .getService("extphone"));
+        try {
+            Log.d(this, "setLocalCallHold" + subscriptionId);
+            mExtTelephony.setLocalCallHold(Integer.parseInt(subscriptionId), enable);
+        } catch (NullPointerException ex) {
+            Log.d(this, "setLocalCallHold" + ex);
+        } catch (RemoteException ex) {
+            Log.d(this, "setLocalCallHold" + ex);
+        } catch (NumberFormatException ex) {
+            Log.d(this, "setLocalCallHold" + ex);
+        }
+    }
+
+    public void clearPendingMOEmergencyCall() {
+        mPendingMOEmerCall = null;
+        mDisconnectingCall = null;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index a4c76c1..fee52a7 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -82,6 +82,10 @@
     }
 
     @Override
+    public void onSessionModifyRequestSent(VideoProfile fromProfile, VideoProfile toProfile) {
+    }
+
+    @Override
     public void onHoldToneRequested(Call call) {
     }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 6ec8945..f040134 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -37,6 +37,7 @@
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IConnectionService;
@@ -314,6 +315,10 @@
                             childCall.setParentCall(null);
                         } else {
                             Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
+                            if (conferenceCall.getTargetPhoneAccount() == null) {
+                                PhoneAccountHandle ph = childCall.getTargetPhoneAccount();
+                                conferenceCall.setTargetPhoneAccount(ph);
+                            }
                             childCall.setParentCall(conferenceCall);
                         }
                     } else {
@@ -555,7 +560,14 @@
                 synchronized (mLock) {
                     Bundle.setDefusable(extras, true);
                     Call call = mCallIdMapper.getCall(callId);
-                    if (call != null) {
+                    if (call != null && extras != null) {
+                        if (extras.getParcelable(TelephonyManager.EMR_DIAL_ACCOUNT) instanceof
+                                    PhoneAccountHandle) {
+                            PhoneAccountHandle account = extras.
+                                    getParcelable(TelephonyManager.EMR_DIAL_ACCOUNT);
+                            Log.d(this, "setTargetPhoneAccount, account = " + account);
+                            call.setTargetPhoneAccount(account);
+                        }
                         call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
                     }
                 }
@@ -739,6 +751,7 @@
                 Log.endSession();
             }
         }
+
     }
 
     private final Adapter mAdapter = new Adapter();
@@ -1013,12 +1026,12 @@
     }
 
     void removeCall(String callId, DisconnectCause disconnectCause) {
+        mCallIdMapper.removeCall(callId);
+
         CreateConnectionResponse response = mPendingResponses.remove(callId);
         if (response != null) {
             response.handleCreateConnectionFailure(disconnectCause);
         }
-
-        mCallIdMapper.removeCall(callId);
     }
 
     void removeCall(Call call, DisconnectCause disconnectCause) {
@@ -1064,6 +1077,17 @@
         }
     }
 
+    void addParticipantWithConference(Call call, String recipients) {
+        final String callId = mCallIdMapper.getCallId(call);
+            if (isServiceValid("addParticipantWithConference")) {
+                try {
+                    logOutgoing("addParticipantWithConference %s, %s", recipients, callId);
+                    mServiceInterface.addParticipantWithConference(callId, recipients);
+                } catch (RemoteException ignored) {
+                }
+        }
+    }
+
     void mergeConference(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("mergeConference")) {
diff --git a/src/com/android/server/telecom/DsdaAdapter.java b/src/com/android/server/telecom/DsdaAdapter.java
new file mode 100644
index 0000000..e31a015
--- /dev/null
+++ b/src/com/android/server/telecom/DsdaAdapter.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2016 The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.server.telecom;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SubscriptionManager;
+
+import org.codeaurora.internal.IDsda;
+
+class DsdaAdapter extends IDsda.Stub {
+    private final CallsManager mCallsManager;
+    public DsdaAdapter(CallsManager callsManager) {
+        mCallsManager = callsManager;
+    }
+
+    public void switchToActiveSub(int sub){
+        Log.w(this, "switchToActiveSub" + sub + " mCallsManager:" + mCallsManager);
+        if (mCallsManager != null) {
+            String subId = (sub == SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                    ? null : String.valueOf(sub);
+            mCallsManager.switchToOtherActiveSub(subId);
+        } else {
+            Log.w(this, "mCallsManager null");
+        }
+        return;
+    }
+
+    public int getActiveSubscription() {
+        String activeSub = mCallsManager.getActiveSubscription();
+        return (activeSub == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID:
+                Integer.parseInt(activeSub);
+    }
+}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index e47f3a2..8a6e1a0 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -493,4 +493,15 @@
              Log.endSession();
         }
     }
+
+    public void switchToOtherActiveSub(String sub) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                mCallsManager.switchToOtherActiveSub(sub);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 2157bf3..33cf329 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -194,7 +194,8 @@
             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
             mIsConnected = true;
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE |
+                        Context.BIND_ABOVE_CLIENT,
                         UserHandle.CURRENT)) {
                 Log.w(this, "Failed to connect.");
                 mIsConnected = false;
@@ -1124,6 +1125,7 @@
         }
         Log.i(this, "%s calls sent to InCallService.", numCallsSent);
         Trace.endSection();
+        mCallsManager.setDsdaAdapter();
         return true;
     }
 
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index e0b0dc0..a916a37 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -70,6 +70,7 @@
     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
     public static final int TONE_VOICE_PRIVACY = 13;
     public static final int TONE_VIDEO_UPGRADE = 14;
+    public static final int TONE_HOLD_RECALL = 15;
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
@@ -202,6 +203,12 @@
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
                     break;
+                case TONE_HOLD_RECALL:
+                    toneType = ToneGenerator.TONE_HOLD_RECALL;
+                    toneVolume = RELATIVE_VOLUME_HIPRI;
+                    // Call hold recall tone is stopped by stopTone() method
+                    toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    break;
                 case TONE_VOICE_PRIVACY:
                     // TODO: fill in.
                     throw new IllegalStateException("Voice privacy tone NYI.");
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index b63baaf..ca0205a 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -32,9 +32,11 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyProperties;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -120,8 +122,8 @@
                         disconnectTimeout = getDisconnectTimeoutFromApp(
                                 getResultExtras(false), disconnectTimeout);
                         endEarly = true;
-                    } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
-                            mContext, resultNumber)) {
+                    } else if (TelephonyUtil.isPotentialLocalEmergencyNumber(
+                            resultNumber)) {
                         Log.w(this, "Cannot modify outgoing call to emergency number %s.",
                                 resultNumber);
                         disconnectTimeout = 0;
@@ -142,12 +144,20 @@
                         return;
                     }
 
-                    Uri resultHandleUri = Uri.fromParts(
-                            mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
-                                    PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
-                            resultNumber, null);
-
+                    boolean isSkipSchemaParsing = mIntent.getBooleanExtra(
+                            TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false);
+                    Uri resultHandleUri = null;
                     Uri originalUri = mIntent.getData();
+                    if (isSkipSchemaParsing) {
+                        // resultNumber does not have the schema present
+                        // hence use originalUri which is same as handle
+                        resultHandleUri = Uri.fromParts(PhoneAccount.SCHEME_TEL,
+                                originalUri.toString(), null);
+                    } else {
+                        resultHandleUri = Uri.fromParts(mPhoneNumberUtilsAdapter
+                                .isUriNumber(resultNumber) ? PhoneAccount.SCHEME_SIP
+                                : PhoneAccount.SCHEME_TEL, resultNumber, null);
+                    }
 
                     if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
                         Log.v(this, "Call number unmodified after" +
@@ -225,13 +235,20 @@
         }
 
         String number = mPhoneNumberUtilsAdapter.getNumberFromIntent(intent, mContext);
-        if (TextUtils.isEmpty(number)) {
+        boolean isConferenceUri = intent.getBooleanExtra(
+                TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false);
+        if (!isConferenceUri && TextUtils.isEmpty(number)) {
             Log.w(this, "Empty number obtained from the call intent.");
             return DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
         }
 
         boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
-        if (!isUriNumber) {
+        boolean isSkipSchemaParsing = intent.getBooleanExtra(
+                TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false);
+        Log.v(this,"processIntent isConferenceUri: " + isConferenceUri +
+                " isSkipSchemaParsing = "+isSkipSchemaParsing);
+
+        if (!isUriNumber && !isConferenceUri && !isSkipSchemaParsing) {
             number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
             number = mPhoneNumberUtilsAdapter.stripSeparators(number);
         }
@@ -277,6 +294,11 @@
             int videoState = mIntent.getIntExtra(
                     TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     VideoProfile.STATE_AUDIO_ONLY);
+            // Since we will not start NewOutgoingCallBroadcastIntentReceiver in case of
+            // callImmediately is true, make sure to mark it as ready, so that when user
+            // selects account, call can go ahead in case of numbers which are potential emergency
+            // but not actual emergeny.
+            mCall.setNewOutgoingCallIntentBroadcastIsDone();
             mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, number, null), null,
                     speakerphoneOn, videoState);
 
@@ -288,7 +310,11 @@
 
         UserHandle targetUser = mCall.getInitiatingUser();
         Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
-        broadcastIntent(intent, number, !callImmediately, targetUser);
+        if (isSkipSchemaParsing) {
+            broadcastIntent(intent, handle.toString(), !callImmediately, targetUser);
+        } else {
+            broadcastIntent(intent, number, !callImmediately, targetUser);
+        }
         return DisconnectCause.NOT_DISCONNECTED;
     }
 
@@ -423,8 +449,7 @@
      */
     private boolean isPotentialEmergencyNumber(String number) {
         Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
-        return (number != null)
-                && mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext, number);
+        return (number != null) && TelephonyUtil.isPotentialLocalEmergencyNumber(number);
     }
 
     /**
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index c4ea9cf..386f36f 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -98,10 +98,7 @@
             properties |= android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL;
         }
 
-        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
-        boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
-                phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
-        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
+        if (call.isRespondViaSmsCapable()) {
             capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
         }
 
@@ -292,7 +289,13 @@
         android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
 
         Connection.CAPABILITY_CAN_PULL_CALL,
-        android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL
+        android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL,
+
+        Connection.CAPABILITY_VOICE_PRIVACY,
+        android.telecom.Call.Details.CAPABILITY_VOICE_PRIVACY,
+
+        Connection.CAPABILITY_ADD_PARTICIPANT,
+        android.telecom.Call.Details.CAPABILITY_ADD_PARTICIPANT
     };
 
     private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index ef09939..76b89a0 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -72,6 +72,7 @@
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -708,6 +709,9 @@
         for (Listener l : mListeners) {
             l.onDefaultOutgoingChanged(this);
         }
+
+        Intent intent = new Intent("codeaurora.intent.action.DEFAULT_PHONE_ACCOUNT_CHANGED");
+        mContext.sendBroadcast(intent);
     }
 
     private String getAccountDiffString(PhoneAccount account1, PhoneAccount account2) {
@@ -878,6 +882,31 @@
                 includeDisabledAccounts, userHandle)) {
             handles.add(account.getAccountHandle());
         }
+
+        Collections.sort(handles, new Comparator<PhoneAccountHandle>() {
+            public int compare(PhoneAccountHandle accountHandle1
+                        , PhoneAccountHandle accountHandle2) {
+                TelephonyManager tm = (TelephonyManager) mContext
+                        .getSystemService(Context.TELEPHONY_SERVICE);
+                int max = tm.getPhoneCount();
+                int phoneId1 = max;
+                int phoneId2 = max;
+                try {
+                    phoneId1 = SubscriptionManager
+                            .getPhoneId(Integer.parseInt(accountHandle1.getId()));
+                } catch (NumberFormatException e) {
+                    Log.e(this, e, "Could not parse subId1 " + accountHandle1.getId());
+                }
+                try {
+                    phoneId2 = SubscriptionManager
+                            .getPhoneId(Integer.parseInt(accountHandle2.getId()));
+                } catch (NumberFormatException e) {
+                    Log.e(this, e, "Could not parse subId2 " + accountHandle2.getId());
+                }
+                return (phoneId1 < phoneId2 ? -1 : (phoneId1 == phoneId2 ? 0 : 1));
+            }
+        });
+
         return handles;
     }
 
@@ -1490,9 +1519,9 @@
                                         "Could not parse UserHandle " + userSerialNumberString);
                             }
                         }
-                        if (accountHandle != null && userHandle != null && groupId != null) {
-                            return new DefaultPhoneAccountHandle(userHandle, accountHandle,
-                                    groupId);
+                        if (accountHandle != null && userHandle != null) {
+                            return new DefaultPhoneAccountHandle(userHandle, accountHandle
+                                    , (groupId != null) ? groupId : "");
                         }
                     }
                     return null;
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 7609b08..3f60dbb 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -37,6 +37,7 @@
 import android.telephony.TelephonyManager;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.TextUtils;
 import android.widget.Toast;
 
 import java.util.ArrayList;
@@ -136,7 +137,7 @@
             int subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(
                     call.getTargetPhoneAccount());
             rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
-                    textMessage, subId);
+                    textMessage, subId, call.getName());
         }
     }
 
@@ -175,8 +176,8 @@
      * Reject the call with the specified message. If message is null this call is ignored.
      */
     private void rejectCallWithMessage(Context context, String phoneNumber, String textMessage,
-            int subId) {
-        if (textMessage != null) {
+            int subId, String contactName) {
+        if (textMessage != null && !TextUtils.isEmpty(textMessage)) {
             final ComponentName component =
                     SmsApplication.getDefaultRespondViaMessageApplication(context,
                             true /*updateIfNeeded*/);
@@ -190,7 +191,7 @@
                 }
 
                 SomeArgs args = SomeArgs.obtain();
-                args.arg1 = phoneNumber;
+                args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber;
                 args.arg2 = context;
                 mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
                 intent.setComponent(component);
diff --git a/src/com/android/server/telecom/RespondViaSmsSettings.java b/src/com/android/server/telecom/RespondViaSmsSettings.java
index f8aa568..80c69d8 100644
--- a/src/com/android/server/telecom/RespondViaSmsSettings.java
+++ b/src/com/android/server/telecom/RespondViaSmsSettings.java
@@ -25,6 +25,7 @@
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
+import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuItem;
 
@@ -101,6 +102,10 @@
         // (Watch out: onPreferenceChange() is called *before* the
         // Preference itself gets updated, so we need to use newValue here
         // rather than pref.getText().)
+        String quickResponse = ((String) newValue).trim();
+        if (TextUtils.isEmpty(quickResponse)) {
+            return false;
+        }
         pref.setTitle((String) newValue);
 
         // Save the new preference value.
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index c5db6de..f198f93 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.server.telecom.ui.ViceNotificationImpl;
 
 public final class TelecomBroadcastIntentProcessor {
     /** The action used to send SMS response for the missed call notification. */
@@ -34,6 +37,8 @@
             "com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
 
     public static final String EXTRA_USERHANDLE = "userhandle";
+    public static final String ACTION_CALL_PULL =
+            "org.codeaurora.ims.ACTION_CALL_PULL";
 
     private final Context mContext;
     private final CallsManager mCallsManager;
@@ -79,6 +84,21 @@
         // Clear the missed call notification and call log entries.
         } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
             missedCallNotifier.clearMissedCalls(userHandle);
+        } else if (ACTION_CALL_PULL.equals(action)) {
+            // Close the notification shade and the notification itself.
+            closeSystemDialogs(mContext);
+
+            String dialogId =  intent.getStringExtra("org.codeaurora.ims.VICE_CLEAR");
+            int callType =  intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 0);
+            Log.i(this,"ACTION_CALL_PULL: calltype = " + callType + ", dialogId = " + dialogId);
+
+            Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+            callIntent.putExtra(TelephonyProperties.EXTRA_IS_CALL_PULL, true);
+            callIntent.putExtra(TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, true);
+            callIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, callType);
+            callIntent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            mContext.startActivityAsUser(callIntent, userHandle);
         }
     }
 
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 24d925d..99895e5 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -23,6 +23,7 @@
 import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
 import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
+import com.android.server.telecom.ui.ViceNotificationImpl;
 
 import android.Manifest;
 import android.content.BroadcastReceiver;
@@ -101,6 +102,7 @@
     private final TelecomServiceImpl mTelecomServiceImpl;
     private final ContactsAsyncHelper mContactsAsyncHelper;
     private final DialerCodeReceiver mDialerCodeReceiver;
+    private final ViceNotifier mViceNotifier;
 
     private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
         @Override
@@ -155,6 +157,7 @@
                     bluetoothPhoneServiceImplFactory,
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
+            ViceNotifier vicenotifier,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             InterruptionFilterProxy interruptionFilterProxy) {
         mContext = context.getApplicationContext();
@@ -162,6 +165,7 @@
         Log.initMd5Sum();
 
         Log.startSession("TS.init");
+        mViceNotifier = vicenotifier;
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
         mContactsAsyncHelper = new ContactsAsyncHelper(
                 new ContactsAsyncHelper.ContentResolverAdapter() {
@@ -200,6 +204,7 @@
                 defaultDialerAdapter,
                 timeoutsAdapter,
                 asyncRingtonePlayer,
+                mViceNotifier,
                 phoneNumberUtilsAdapter,
                 interruptionFilterProxy);
 
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index 50b8901..f2c5340 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -19,6 +19,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
+import android.os.ServiceManager;
+import android.os.RemoteException;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
@@ -26,6 +28,7 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import org.codeaurora.internal.IExtTelephony;
 
 import java.util.Collections;
 import java.util.Comparator;
@@ -41,6 +44,8 @@
     private static final String PSTN_CALL_SERVICE_CLASS_NAME =
             "com.android.services.telephony.TelephonyConnectionService";
 
+    private static final String LOG_TAG = "TelephonyUtil";
+
     private static final PhoneAccountHandle DEFAULT_EMERGENCY_PHONE_ACCOUNT_HANDLE =
             new PhoneAccountHandle(
                     new ComponentName(TELEPHONY_PACKAGE_NAME, PSTN_CALL_SERVICE_CLASS_NAME), "E");
@@ -70,8 +75,35 @@
     }
 
     public static boolean shouldProcessAsEmergency(Context context, Uri handle) {
-        return handle != null && PhoneNumberUtils.isLocalEmergencyNumber(
-                context, handle.getSchemeSpecificPart());
+        return handle != null && isLocalEmergencyNumber(handle.getSchemeSpecificPart());
+    }
+
+    public static boolean isLocalEmergencyNumber(String address) {
+        IExtTelephony mIExtTelephony =
+            IExtTelephony.Stub.asInterface(ServiceManager.getService("extphone"));
+        boolean result = false;
+        try {
+            result = mIExtTelephony.isLocalEmergencyNumber(address);
+        }catch (RemoteException ex) {
+            Log.e(LOG_TAG, ex, "RemoteException");
+        } catch (NullPointerException ex) {
+            Log.e(LOG_TAG, ex, "NullPointerException");
+        }
+        return result;
+    }
+
+    public static boolean isPotentialLocalEmergencyNumber(String address) {
+        IExtTelephony mIExtTelephony =
+            IExtTelephony.Stub.asInterface(ServiceManager.getService("extphone"));
+        boolean result = false;
+        try {
+            result = mIExtTelephony.isPotentialLocalEmergencyNumber(address);
+        }catch (RemoteException ex) {
+            Log.e(LOG_TAG, ex, "RemoteException");
+        } catch (NullPointerException ex) {
+            Log.e(LOG_TAG, ex, "NullPointerException");
+        }
+        return result;
     }
 
     public static void sortSimPhoneAccounts(Context context, List<PhoneAccount> accounts) {
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index 25284e4..aec29be 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -16,6 +16,9 @@
 
 package com.android.server.telecom;
 
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -35,12 +38,17 @@
     private final WiredHeadsetManager mWiredHeadsetManager;
     private int mPreferredTtyMode = TelecomManager.TTY_MODE_OFF;
     private int mCurrentTtyMode = TelecomManager.TTY_MODE_OFF;
+    protected NotificationManager mNotificationManager;
+
+    static final int HEADSET_PLUGIN_NOTIFICATION = 1000;
 
     TtyManager(Context context, WiredHeadsetManager wiredHeadsetManager) {
         mContext = context;
         mWiredHeadsetManager = wiredHeadsetManager;
         mWiredHeadsetManager.addListener(this);
 
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mPreferredTtyMode = Settings.Secure.getInt(
                 mContext.getContentResolver(),
                 Settings.Secure.PREFERRED_TTY_MODE,
@@ -67,6 +75,12 @@
     public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
         Log.v(this, "onWiredHeadsetPluggedInChanged");
         updateCurrentTtyMode();
+
+        if (newIsPluggedIn) {
+            showHeadSetPlugin();
+        } else {
+            cancelHeadSetPlugin();
+        }
     }
 
     private void updateCurrentTtyMode() {
@@ -109,6 +123,33 @@
         audioManager.setParameters("tty_mode=" + audioTtyMode);
     }
 
+    void showHeadSetPlugin() {
+        Log.v(TtyManager.this, "showHeadSetPlugin()...");
+
+        String titleText = mContext.getString(
+                R.string.headset_plugin_view_title);
+        String expandedText = mContext.getString(
+                R.string.headset_plugin_view_text);
+
+        Notification notification = new Notification();
+        notification.icon = android.R.drawable.stat_sys_headset;
+        notification.flags |= Notification.FLAG_NO_CLEAR;
+        notification.tickerText = titleText;
+
+        // create the target network operators settings intent
+        Intent intent = new Intent("android.intent.action.NO_ACTION");
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        notification.setLatestEventInfo(mContext, titleText, expandedText, pi);
+        mNotificationManager.notify(HEADSET_PLUGIN_NOTIFICATION, notification);
+    }
+
+    void cancelHeadSetPlugin() {
+        Log.v(TtyManager.this, "cancelHeadSetPlugin()...");
+        mNotificationManager.cancel(HEADSET_PLUGIN_NOTIFICATION);
+    }
+
     private final class TtyBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/src/com/android/server/telecom/ViceNotifier.java b/src/com/android/server/telecom/ViceNotifier.java
new file mode 100644
index 0000000..639ae98
--- /dev/null
+++ b/src/com/android/server/telecom/ViceNotifier.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014-2016 The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux FOundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+import com.android.server.telecom.ui.ViceNotificationImpl;
+
+/**
+ * Creates a notification for calls that are ongoing on secondary device
+ */
+public interface ViceNotifier {
+
+    ViceNotificationImpl create(Context context, CallsManager callsManager);
+
+}
+
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 3722b59..3dfec6f 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -47,6 +47,7 @@
      */
     interface Listener {
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+        void onSessionModifyRequestSent(VideoProfile fromProfile, VideoProfile toProfile);
     }
 
     /**
@@ -112,6 +113,13 @@
         mCall = call;
     }
 
+    public void clearVideoCallback() {
+        try {
+            mConectionServiceVideoProvider.removeVideoCallback(mVideoCallListenerBinder);
+        } catch (RemoteException e) {
+        }
+    }
+
     /**
      * IVideoCallback stub implementation.  An instance of this class receives callbacks from the
      * {@code ConnectionService}'s video provider.
@@ -378,6 +386,10 @@
                     toProfile.getVideoState());
             try {
                 mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile);
+                // Inform other Telecom components of the session modification request.
+                for (Listener listener : mListeners) {
+                    listener.onSessionModifyRequestSent(fromProfile, toProfile);
+                }
             } catch (RemoteException e) {
             }
         }
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 1aaae46..ea64eb3 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -61,6 +61,14 @@
                             Log.event(call, Log.Events.DIRECT_TO_VM_FINISHED, result);
                             callback.onCallFilteringComplete(call, result);
                         } else {
+                            result = new CallFilteringResult(
+                                    true, // shouldAllowCall
+                                    false, // shouldReject
+                                    true, // shouldAddToCallLog
+                                    true // shouldShowNotification
+                            );
+                            Log.event(call, Log.Events.DIRECT_TO_VM_FINISHED, result);
+                            callback.onCallFilteringComplete(call, result);
                             Log.w(this, "CallerInfo lookup returned with a different handle than " +
                                     "what was passed in. Was %s, should be %s", handle, callHandle);
                         }
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 92906e2..f6080f8 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -49,7 +49,9 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ViceNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
+import com.android.server.telecom.ui.ViceNotificationImpl;
 
 /**
  * Implementation of the ITelecom interface.
@@ -159,6 +161,14 @@
                             },
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(),
+                            new ViceNotifier() {
+                                @Override
+                                public ViceNotificationImpl create(Context context,
+                                        CallsManager callsManager) {
+                                    return new ViceNotificationImpl(
+                                            context.getApplicationContext(), callsManager);
+                                }
+                            },
                             new PhoneNumberUtilsAdapterImpl(),
                             new InterruptionFilterProxy() {
                                 @Override
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 16d4249..af80f6c 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -604,8 +604,12 @@
         };
 
         // setup query spec, look for all Missed calls that are new.
-        StringBuilder where = new StringBuilder("type=");
+        StringBuilder where = new StringBuilder("(type=");
         where.append(Calls.MISSED_TYPE);
+        where.append(" OR type=");
+        where.append(Calls.MISSED_IMS_TYPE);
+        where.append(" OR type=");
+        where.append(Calls.MISSED_WIFI_TYPE+")");
         where.append(" AND new=1");
         where.append(" AND is_read=0");
 
diff --git a/src/com/android/server/telecom/ui/ViceNotificationImpl.java b/src/com/android/server/telecom/ui/ViceNotificationImpl.java
new file mode 100644
index 0000000..b08f0c9
--- /dev/null
+++ b/src/com/android/server/telecom/ui/ViceNotificationImpl.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2014-2016 The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux FOundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.telecom.ui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+
+import org.codeaurora.ims.internal.IQtiImsExt;
+import org.codeaurora.ims.QtiImsException;
+import org.codeaurora.ims.QtiImsExtListenerBaseImpl;
+import org.codeaurora.ims.QtiImsExtManager;
+import org.codeaurora.ims.QtiViceInfo;
+import org.codeaurora.ims.utils.QtiImsExtUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Handles the VICE notifications on the statusbar
+ * And when statusbar is expanded
+ */
+public class ViceNotificationImpl extends CallsManagerListenerBase {
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private IQtiImsExt mQtiImsExt = null;
+    private boolean mImsServiceBound = false;
+    private Notification.Builder mBuilder = null;
+    private Notification.Builder mPublicNotificationBuilder = null;
+    private UserHandle mCurrentUserHandle;
+
+    private static final String LOG_TAG = "ViceNotificationImpl";
+    private MyHandler mHandler = new MyHandler();
+
+    /**
+     * Holds Pullable and NonPullable calls passed from IMS Service
+     * Each String[] contains call info in following order
+     * Number, Pullable/NonPullable, CallType, Direction
+     * Ex: For callInfo[] -
+     * callInfo[QtiViceInfo.INDEX_DIALOG_ID] - Holds Unique DialogId
+     * callInfo[QtiViceInfo.INDEX_NUMBER] - Holds number/uri
+     * callInfo[QtiViceInfo.INDEX_ISPULLABLE] - Pullable/NonPullable (true, false)
+     * callInfo[QtiViceInfo.INDEX_CALLTYPE] - CallType
+     *     CallType - volteactive, volteheld, vttxrx, vttx, vtrx, vtheld
+     * callInfo[QtiViceInfo.INDEX_DIRECTION] - Direction of the call (Originator/recipent)
+     */
+    private List<String[]> mQtiViceInfo = null;
+
+    // Holds the callInfo which needs to be displayed after the active call ends
+    private  List<String[]> mBackedUpCallList = null;
+
+    private boolean mWasInCall = false;
+
+    // HashMap that holds Dialog Number & Notification Id used to display on the statusbar
+    private Map<String,Integer> mNotification = new HashMap<String,Integer>();
+
+    private ImsIntentReceiver mImsIntentReceiver = null;
+
+    private static final String IMS_SERVICE_PKG_NAME = "org.codeaurora.ims.internal";
+
+    private final TelecomManager mTelecomManager;
+
+    public ViceNotificationImpl(Context context, CallsManager callsManager) {
+        mContext = context;
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        Log.i(this,"ViceNotificationImpl");
+        registerImsReceiver();
+        mTelecomManager = TelecomManager.from(mContext);
+    }
+
+    private class MyHandler extends Handler {
+        static final int MESSAGE_VICE_NOTIFY = 1;
+        static final int MESSAGE_CALL_ENDED = 2;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_VICE_NOTIFY:
+                    Log.i(this,"MESSAGE_VICE_NOTIFY");
+                    resetBeforeProcess();
+                    processNotification();
+                    break;
+
+                case MESSAGE_CALL_ENDED:
+                    if (mWasInCall) {
+                        Log.i(this,"MESSAGE_CALL_ENDED");
+                        resetBeforeProcess();
+                        mQtiViceInfo = mBackedUpCallList;
+                        processNotification();
+                    }
+                    break;
+
+                default:
+                    Log.i(this,"VICE default");
+            }
+        }
+    }
+
+    private class ImsIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.i("ViceNotificationImpl", "mImsIntentReceiver: action " + intent.getAction());
+            if (ImsManager.ACTION_IMS_SERVICE_UP.equals(intent.getAction())) {
+                registerForViceRefreshInfo();
+            }
+        }
+    }
+
+    // Clear the existing notifications on statusbar and
+    // Hashmap whenever new Vice Notification is received
+    private void resetBeforeProcess() {
+        mBuilder = null;
+        mPublicNotificationBuilder = null;
+        mWasInCall = false;
+        checkAndUpdateNotification();
+    }
+
+    /* QtiImsExtListenerBaseImpl instance to handle call backs */
+    private QtiImsExtListenerBaseImpl imsInterfaceListener =
+        new QtiImsExtListenerBaseImpl() {
+
+        @Override
+        public void notifyRefreshViceInfo(QtiViceInfo qtiViceInfo) {
+            mQtiViceInfo = null;
+            processViceCallInfo(qtiViceInfo);
+        }
+    };
+
+    public void registerForViceRefreshInfo() {
+        try {
+            QtiImsExtManager.getInstance().registerForViceRefreshInfo(imsInterfaceListener);
+        } catch (QtiImsException e) {
+            Log.i(this, "registerForViceRefreshInfo failed. Exception = " + e);
+        }
+    }
+
+    private void processViceCallInfo(QtiViceInfo qtiViceInfo) {
+        mQtiViceInfo = new ArrayList<String[]>();
+        mQtiViceInfo = qtiViceInfo.callInfo;
+        // Post an event to self handler
+        mHandler.sendEmptyMessage(MyHandler.MESSAGE_VICE_NOTIFY);
+    }
+
+    private void resetBuilders() {
+        mBuilder = null;
+        mPublicNotificationBuilder = null;
+        mBuilder = new Notification.Builder(mContext);
+        mPublicNotificationBuilder = new Notification.Builder(mContext);
+    }
+
+    /**
+     * This function does the following -
+     * - Iterate through the callList
+     * - Update the hashmap for notification
+     * - AddAction for pullable calls to update the button "TAP TO PULL"
+     * - Build notification and show it
+     *
+     * "Tap to Pull" Button should be seen only if -
+     * - Phone is in idle state AND
+     * - CallState received is ACTIVE for Voice calls or ACTIVE/SENDONLY/RECVONLY for VT AND
+     * - If Volte/VT is supported on device AND
+     * - IsPullable is true
+     */
+    private void processNotification() {
+        Random random = new Random();
+        int notifId = 0;
+        boolean isVt = false;
+
+        if ((mQtiViceInfo != null) && !mQtiViceInfo.isEmpty()) {
+            Log.i(this, "processNotification : Number of Calls = "
+                    + mQtiViceInfo.size() + ", notif = " + notifId);
+
+            for (int i = 0; i < mQtiViceInfo.size(); i++) {
+                notifId = random.nextInt(500);
+                String[] callInfo = new String[QtiViceInfo.INDEX_MAX];
+                callInfo = mQtiViceInfo.get(i);
+                Log.i(this, "processNotification callInfo[" + i + "] = "
+                        + ", DialogId = " + callInfo[QtiViceInfo.INDEX_DIALOG_ID]
+                        + ", Number = " + callInfo[QtiViceInfo.INDEX_NUMBER]
+                        + ", Pullable = " + callInfo[QtiViceInfo.INDEX_ISPULLABLE]
+                        + ", CallType = " + callInfo[QtiViceInfo.INDEX_CALLTYPE]
+                        + ", Direction = " + callInfo[QtiViceInfo.INDEX_DIRECTION]
+                        + ", notifId = " + notifId);
+
+                resetBuilders();
+                Log.i(this, "processNotification isInCall = " + getTelecomManager().isInCall());
+                isVt = isVtCall(callInfo[QtiViceInfo.INDEX_CALLTYPE]);
+
+                // Once the active call ends, we need to display "Tap to pull" option
+                // for the pullable calls. Hence backup the callinfo
+                if (getTelecomManager().isInCall()) {
+                    backupInfoToProcessLater();
+                }
+                // Refer comments in API description
+                if (!(getTelecomManager().isInCall()) &&
+                        callInfo[QtiViceInfo.INDEX_ISPULLABLE].equalsIgnoreCase("true") &&
+                        isDeviceCapableOfPull(callInfo[QtiViceInfo.INDEX_CALLTYPE])) {
+                    addAction(callInfo[QtiViceInfo.INDEX_NUMBER], isVt,
+                            callInfo[QtiViceInfo.INDEX_CALLTYPE],
+                            callInfo[QtiViceInfo.INDEX_DIALOG_ID]);
+                } else {
+                    addAction(null, false, null, null);
+                }
+                updateLargeIconforCallType(isVt);
+
+                showNotification(callInfo[QtiViceInfo.INDEX_NUMBER],
+                        callInfo[QtiViceInfo.INDEX_DIALOG_ID], notifId);
+            }
+        } else {
+            Log.i(this, "processNotification DEP null");
+        }
+    }
+
+    private boolean isDeviceCapableOfPull(String callType) {
+        return ((isVtCall(callType) && isVTPullAllowed()) ||
+                ((callType != null) && callType.equalsIgnoreCase(
+                QtiViceInfo.CALL_TYPE_VOICE_ACTIVE) && isVoltePullAllowed()));
+    }
+
+    private boolean isVTPullAllowed() {
+        return TelephonyManager.getDefault().isVideoCallingEnabled();
+    }
+
+    private boolean isVoltePullAllowed() {
+        return TelephonyManager.getDefault().isVolteAvailable()
+                || TelephonyManager.getDefault().isWifiCallingAvailable();
+    }
+
+    private boolean isVtCall(String callType) {
+        if ((callType != null) && (callType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_TX_RX) ||
+                callType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_TX) ||
+                callType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_RX) ||
+                callType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_HELD))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * After the active call disconnects, need to refresh the notification
+     * to show pullable calls. Hence save the call information
+     *
+     */
+    private void backupInfoToProcessLater() {
+        mBackedUpCallList = new ArrayList<String[]>();
+        mBackedUpCallList = mQtiViceInfo;
+        // Call can be ended either through "Tap To Pull" or existing call ending
+        // Reset the variables only for later case
+        // Use this boolean to track the END reason
+        mWasInCall = true;
+    }
+
+    /**
+     * Retrieve all notifications from the map.
+     * Cancel and remove all notifications from the map.
+     * CancelAll not used as it is an asynchronous call and can cause issue with
+     * back to back notifications.
+     */
+    private void checkAndUpdateNotification() {
+        Set<Map.Entry<String, Integer>> call = mNotification.entrySet();
+        if ((call == null) || (mNotification.isEmpty())) {
+            return;
+        }
+
+        Iterator<Map.Entry<String, Integer>> iterator = call.iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, Integer> entry = iterator.next();
+            String dialog = entry.getKey();
+            Integer notifId = entry.getValue();
+            mNotificationManager.cancel(notifId);
+            iterator.remove();
+        }
+    }
+
+    /**
+     * This API should be invoked only for pullable calls
+     * Responsible to add "TAP to Pull" button, using which call
+     * can be pulled
+     */
+    private void addAction(String uri, boolean isVt, String callType, String dialogId) {
+        Log.i(this, "addAction dialogId = " + dialogId + ", isVt = " + isVt
+                + ", callType = " + callType);
+        if (uri != null) {
+            mBuilder.addAction(R.drawable.ic_phone_24dp,
+                    mContext.getString(R.string.pull_to_call_back),
+                    createCallBackPendingIntent(Uri.parse(uri), isVt, callType, dialogId));
+            mBuilder.setPriority(Notification.PRIORITY_HIGH);
+        }
+    }
+
+    /*
+     * When the notification bar is pulled,
+     * Update - Video icon for VT Calls
+     *        - Phone icon for Volte calls
+     */
+    private void updateLargeIconforCallType(boolean isVt) {
+       Log.i(this, "updateLargeIconforCallType isVt  = " + isVt);
+       Bitmap bitmap;
+       if (isVt) {
+            bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+                    R.drawable.video_icon);
+       } else {
+            bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+                    R.drawable.volte_icon);
+       }
+       mBuilder.setLargeIcon(bitmap);
+    }
+
+    /*
+     * When the phone is pattern locked, display only restricted information
+     * for this notification
+     */
+    private void buildPublicNotification() {
+        // Update the text in the public version as well
+        mPublicNotificationBuilder
+                .setContentTitle(mContext.getString(R.string.notification_pullcall))
+                .setAutoCancel(true)
+                .setColor(com.android.internal.R.color.system_notification_accent_color);
+    }
+
+    // Builds & displays the notification on statusbar
+    private void showNotification(String uri, String dialog, int notifId) {
+        PendingIntent contentIntent = PendingIntent.getActivity( mContext,
+                0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
+
+        buildPublicNotification();
+
+        mBuilder.setContentText(uri)
+                .setColor(Color.BLUE)
+                .setVisibility(Notification.VISIBILITY_PRIVATE)
+                .setPublicVersion(mPublicNotificationBuilder.build())
+                .setDeleteIntent(contentIntent);
+
+        // For 1st notification, display the icon on the statusbar
+        // For subsequent notification, dont display the icon
+        Log.i(this," showNotification size = " + mNotification.size() + ", notifid = " + notifId);
+        if (mNotification.size() == 0) {
+            mBuilder.setSmallIcon(R.drawable.ic_phone_24dp);
+        } else {
+            mBuilder.setSmallIcon(android.R.color.transparent);
+        }
+
+        mNotificationManager.notify(notifId, mBuilder.build());
+        // Add the Dialog+Notification ID to hashmap
+        mNotification.put(dialog, notifId);
+    }
+
+    private PendingIntent createCallBackPendingIntent(Uri handle, boolean isVt,
+        String callType, String dialogId) {
+        return createTelecomPendingIntent(
+                TelecomBroadcastIntentProcessor.ACTION_CALL_PULL, handle, isVt, callType, dialogId);
+    }
+
+    /**
+     * Creates generic pending intent from the specified parameters to be received by
+     * {@link TelecomBroadcastIntentProcessor}.
+     *
+     * @param action The intent action.
+     * @param data The intent data.
+     */
+    private PendingIntent createTelecomPendingIntent(String action, Uri data,
+            boolean isVt, String callType, String dialogId) {
+        Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
+        // Add following extras so that Dial request is placed for CallPull
+        // And parsing is avoided for dialstring
+        intent.putExtra("org.codeaurora.ims.VICE_CLEAR", dialogId);
+        if (isVt) {
+            // Extra to start Dial with VT enabled
+            intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    covertStringToIntVtType(callType));
+        } else {
+            intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_AUDIO_ONLY);
+        }
+
+        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE,
+                UserHandle.of(UserHandle.myUserId()));
+
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+
+    private int covertStringToIntVtType(String vtType) {
+        if (vtType != null) {
+            if (vtType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_TX_RX)) {
+                return VideoProfile.STATE_BIDIRECTIONAL;
+            } else if (vtType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_TX)) {
+                return VideoProfile.STATE_TX_ENABLED;
+            } else if (vtType.equalsIgnoreCase(QtiViceInfo.CALL_TYPE_VIDEO_RX)) {
+                return VideoProfile.STATE_RX_ENABLED;
+            } else {
+                return VideoProfile.STATE_AUDIO_ONLY;
+            }
+        } else {
+            Log.i(this, "covertStringToIntVtType vttype null!!");
+            return VideoProfile.STATE_AUDIO_ONLY;
+        }
+    }
+
+    private TelecomManager getTelecomManager() {
+        return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+    }
+
+    /**
+     * During any ongoing calls, if DEP is received with pullable calls,
+     * need to change them to non-pullable. But after calls ends, need to
+     * make them as pullable again.
+     */
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        if ((newState == CallState.ACTIVE) || (newState == CallState.DISCONNECTED)) {
+            Log.i(this, "onCallStateChanged newState = " + newState);
+            backupInfoToProcessLater();
+            mHandler.sendEmptyMessage(MyHandler.MESSAGE_CALL_ENDED);
+        }
+    }
+
+    private void registerImsReceiver() {
+        mImsIntentReceiver = new ImsIntentReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ImsManager.ACTION_IMS_SERVICE_UP);
+        mContext.registerReceiver(mImsIntentReceiver, filter);
+    }
+
+    private void unregisterImsReceiver() {
+        if (mImsIntentReceiver != null) {
+            mContext.unregisterReceiver(mImsIntentReceiver);
+            mImsIntentReceiver = null;
+        }
+    }
+}
diff --git a/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl b/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
new file mode 100644
index 0000000..df4c66a
--- /dev/null
+++ b/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *  * Neither the name of The Linux Foundation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.btmultisim;
+
+/**
+ * Interface used to interact with BluetoothDsdaService to handle DSDA.
+ *
+ * {@hide}
+ */
+interface IBluetoothDsdaService {
+    void setCurrentSub(int sub);
+    void phoneSubChanged();
+    void handleMultiSimPreciseCallStateChange(int ForegroundCallState,
+            int RingingCallState, String RingingNumber, int NumberType,
+            int BackgroundCallState, int numHeldCallsonSub);
+    void processQueryPhoneState();
+    int getTotalCallsOnSub(int subId);
+    boolean isSwitchSubAllowed();
+    void switchSub();
+    boolean canDoCallSwap();
+    boolean hasCallsOnBothSubs();
+    boolean isFakeMultiPartyCall();
+    boolean answerOnThisSubAllowed();
+}
+
diff --git a/tests/Android.mk b/tests/Android.mk
index 8a8113b..56bc04f 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -22,6 +22,7 @@
         android-support-v4 \
         guava \
         mockito-target \
+        ims-ext-common \
         platform-test-annotations
 
 LOCAL_SRC_FILES := \
@@ -33,13 +34,19 @@
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../proto/
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 
+LOCAL_SRC_FILES += \
+        src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
+
+
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
     $(LOCAL_PATH)/../res
 
 LOCAL_JAVA_LIBRARIES := \
         android.test.runner \
-        telephony-common
+        telephony-common \
+        telephony-ext \
+        ims-common
 
 LOCAL_AAPT_FLAGS := \
     --auto-add-overlay \
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index fad8b7e..698da26 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -304,6 +304,9 @@
         }
 
         @Override
+        public void addParticipantWithConference(String callId, String recipients) {}
+
+        @Override
         public IBinder asBinder() {
             return this;
         }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index d1c0325..cf17b5c 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -87,6 +87,8 @@
 
 import com.google.common.base.Predicate;
 
+import com.android.server.telecom.ViceNotifier;
+import com.android.server.telecom.ui.ViceNotificationImpl;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
@@ -192,6 +194,7 @@
     @Mock InCallWakeLockController mInCallWakeLockController;
     @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
     @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
+    @Mock ViceNotifier mViceNotifier;
     @Mock InterruptionFilterProxy mInterruptionFilterProxy;
 
     final ComponentName mInCallServiceComponentNameX =
@@ -406,6 +409,7 @@
                 },
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
+                mViceNotifier,
                 mPhoneNumberUtilsAdapter,
                 mInterruptionFilterProxy);
 
diff --git a/tests/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl b/tests/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
new file mode 100644
index 0000000..df4c66a
--- /dev/null
+++ b/tests/src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *  * Neither the name of The Linux Foundation nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.codeaurora.btmultisim;
+
+/**
+ * Interface used to interact with BluetoothDsdaService to handle DSDA.
+ *
+ * {@hide}
+ */
+interface IBluetoothDsdaService {
+    void setCurrentSub(int sub);
+    void phoneSubChanged();
+    void handleMultiSimPreciseCallStateChange(int ForegroundCallState,
+            int RingingCallState, String RingingNumber, int NumberType,
+            int BackgroundCallState, int numHeldCallsonSub);
+    void processQueryPhoneState();
+    int getTotalCallsOnSub(int subId);
+    boolean isSwitchSubAllowed();
+    void switchSub();
+    boolean canDoCallSwap();
+    boolean hasCallsOnBothSubs();
+    boolean isFakeMultiPartyCall();
+    boolean answerOnThisSubAllowed();
+}
+