Merge tag 'android-6.0.0_r26' into HEAD

Android 6.0.0 release 26

Conflicts:
	src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
	src/com/android/server/telecom/CallIntentProcessor.java
	src/com/android/server/telecom/CallsManager.java
	src/com/android/server/telecom/components/UserCallIntentProcessor.java

Change-Id: I6282a22460bddf72fe1cd4b178d797c5f82f652e
diff --git a/Android.mk b/Android.mk
index b2fbc16..a300de7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,6 +6,8 @@
 LOCAL_JAVA_LIBRARIES := telephony-common
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += \
+        src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
 
 LOCAL_PACKAGE_NAME := Telecom
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4ae711..43e5ab4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -195,6 +195,10 @@
                 <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="com.android.phone.intent.CLEAR_BLACKLISTED_CALLS" />
+                <action android:name="com.android.phone.intent.CLEAR_BLACKLISTED_MESSAGES" />
+                <action android:name="com.android.phone.REMOVE_BLACKLIST" />
+                <action android:name="android.provider.Telephony.SMS_REJECTED" />
             </intent-filter>
         </receiver>
 
diff --git a/res/drawable-hdpi/ic_block_contact_holo_dark.png b/res/drawable-hdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..4c05fbe
--- /dev/null
+++ b/res/drawable-hdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_block_message_holo_dark.png b/res/drawable-hdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..739559f
--- /dev/null
+++ b/res/drawable-hdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_unblock_contact_holo_dark.png b/res/drawable-hdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..682800c
--- /dev/null
+++ b/res/drawable-hdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_unblock_message_holo_dark.png b/res/drawable-hdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..498d246
--- /dev/null
+++ b/res/drawable-hdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_block_contact_holo_dark.png b/res/drawable-mdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..1daf088
--- /dev/null
+++ b/res/drawable-mdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_block_message_holo_dark.png b/res/drawable-mdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..08aee62
--- /dev/null
+++ b/res/drawable-mdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_unblock_contact_holo_dark.png b/res/drawable-mdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..f60ee16
--- /dev/null
+++ b/res/drawable-mdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_unblock_message_holo_dark.png b/res/drawable-mdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..d9526d0
--- /dev/null
+++ b/res/drawable-mdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_block_contact_holo_dark.png b/res/drawable-xhdpi/ic_block_contact_holo_dark.png
new file mode 100644
index 0000000..afea00f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_block_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_block_message_holo_dark.png b/res/drawable-xhdpi/ic_block_message_holo_dark.png
new file mode 100644
index 0000000..0d78f36
--- /dev/null
+++ b/res/drawable-xhdpi/ic_block_message_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png b/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png
new file mode 100644
index 0000000..659a9a2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_unblock_contact_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_unblock_message_holo_dark.png b/res/drawable-xhdpi/ic_unblock_message_holo_dark.png
new file mode 100644
index 0000000..3cd13c1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_unblock_message_holo_dark.png
Binary files differ
diff --git a/res/mipmap-hdpi/ic_launcher_phone.png b/res/mipmap-hdpi/ic_launcher_phone.png
index 47d7894..aa3f099 100644
--- a/res/mipmap-hdpi/ic_launcher_phone.png
+++ b/res/mipmap-hdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_phone.png b/res/mipmap-mdpi/ic_launcher_phone.png
index 3b333cf..b554b7c 100644
--- a/res/mipmap-mdpi/ic_launcher_phone.png
+++ b/res/mipmap-mdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_phone.png b/res/mipmap-xhdpi/ic_launcher_phone.png
index 020c2fa..5b9aff0 100644
--- a/res/mipmap-xhdpi/ic_launcher_phone.png
+++ b/res/mipmap-xhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_phone.png b/res/mipmap-xxhdpi/ic_launcher_phone.png
index 1594e4e..4e204d4 100644
--- a/res/mipmap-xxhdpi/ic_launcher_phone.png
+++ b/res/mipmap-xxhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_phone.png b/res/mipmap-xxxhdpi/ic_launcher_phone.png
index 8c92ac1..3efbee9 100644
--- a/res/mipmap-xxxhdpi/ic_launcher_phone.png
+++ b/res/mipmap-xxxhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/values-mcc450-mnc05/qticonfig.xml b/res/values-mcc450-mnc05/qticonfig.xml
new file mode 100644
index 0000000..1703cd0
--- /dev/null
+++ b/res/values-mcc450-mnc05/qticonfig.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, 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.
+-->
+
+<resources>
+    <!-- Determines emergency VT call support -->
+    <bool name="config_enable_emergency_video_calling">true</bool>
+</resources>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
new file mode 100644
index 0000000..5766bae
--- /dev/null
+++ b/res/values/cm_strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2013-2014 The CyanogenMod Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Blacklist preferences -->
+    <string name="blacklist_title">Blacklist</string>
+    <string name="blacklist_summary_disabled">Disabled</string>
+    <string name="blacklist_summary">Incoming calls from phone numbers in the blacklist will be blocked</string>
+
+    <!-- Blacklist notifications -->
+    <string name="blacklist_call_notification">Blocked a call from %s</string>
+    <string name="blacklist_call_notification_private_number">Blocked a call from a private number</string>
+    <string name="blacklist_call_notification_unknown_number">Blocked a call from unknown number %s</string>
+    <string name="blacklist_call_notification_multiple"><xliff:g id="count">%d</xliff:g> calls were blocked</string>
+    <string name="blacklist_message_notification">Blocked a message from %s</string>
+    <string name="blacklist_message_notification_private_number">Blocked a message from a private number</string>
+    <string name="blacklist_message_notification_unknown_number">Blocked a message from unknown number %s</string>
+    <string name="blacklist_message_notification_multiple"><xliff:g id="count">%d</xliff:g> messages were blocked</string>
+    <string name="blacklist_notification_list_private">Private</string>
+    <string name="unblock_number">Unblock</string>
+
+</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 365e758..4da9e8a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -38,4 +38,7 @@
 
     <!-- Flag indicating if the tty is enabled -->
     <bool name="tty_enabled">false</bool>
+
+    <!-- DTMF key to be used for LCH hold tone -->
+    <string name="lch_dtmf_key" translatable="false">D</string>
 </resources>
diff --git a/res/values/qticonfig.xml b/res/values/qticonfig.xml
new file mode 100644
index 0000000..f3acba6
--- /dev/null
+++ b/res/values/qticonfig.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2015, 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.
+-->
+
+<resources>
+    <!-- By default emergency VT call is not supported -->
+    <bool name="config_enable_emergency_video_calling">false</bool>
+
+</resources>
diff --git a/src/com/android/server/telecom/Blacklist.java b/src/com/android/server/telecom/Blacklist.java
new file mode 100644
index 0000000..5584e6d
--- /dev/null
+++ b/src/com/android/server/telecom/Blacklist.java
@@ -0,0 +1,88 @@
+package com.android.server.telecom;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Telephony;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.util.HashSet;
+
+/**
+ * This class used to handle the blacklist data. Its
+ * only remaining purpose is legacy data migration
+ */
+class Blacklist {
+    private static class PhoneNumber implements Externalizable {
+        static final long serialVersionUID = 32847013274L;
+        String phone;
+
+        public PhoneNumber() {
+        }
+
+        public void writeExternal(ObjectOutput out) throws IOException {
+        }
+
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            phone = (String) in.readObject();
+        }
+
+        @Override
+        public int hashCode() {
+            return phone != null ? phone.hashCode() : 0;
+        }
+    }
+
+    private static final String BLFILE = "blacklist.dat";
+    private static final int BLFILE_VER = 1;
+
+    public static void migrateOldDataIfPresent(Context context) {
+        ObjectInputStream ois = null;
+        HashSet<PhoneNumber> data = null;
+
+        try {
+            ois = new ObjectInputStream(context.openFileInput(BLFILE));
+            Object o = ois.readObject();
+            if (o != null && o instanceof Integer) {
+                // check the version
+                Integer version = (Integer) o;
+                if (version == BLFILE_VER) {
+                    Object numbers = ois.readObject();
+                    if (numbers instanceof HashSet) {
+                        data = (HashSet<PhoneNumber>) numbers;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            // Do nothing
+        } catch (ClassNotFoundException e) {
+            // Do nothing
+        } finally {
+            if (ois != null) {
+                try {
+                    ois.close();
+                } catch (IOException e) {
+                    // Do nothing
+                }
+                context.deleteFile(BLFILE);
+            }
+        }
+        if (data != null) {
+            ContentResolver cr = context.getContentResolver();
+            ContentValues cv = new ContentValues();
+            cv.put(Telephony.Blacklist.PHONE_MODE, 1);
+
+            for (PhoneNumber number : data) {
+                Uri uri = Uri.withAppendedPath(
+                        Telephony.Blacklist.CONTENT_FILTER_BYNUMBER_URI, number.phone);
+                cv.put(Telephony.Blacklist.NUMBER, number.phone);
+                cr.update(uri, cv, null, null);
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/BlacklistCallNotifier.java b/src/com/android/server/telecom/BlacklistCallNotifier.java
new file mode 100644
index 0000000..6e6a527
--- /dev/null
+++ b/src/com/android/server/telecom/BlacklistCallNotifier.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.*;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.style.RelativeSizeSpan;
+import com.android.internal.telephony.util.BlacklistUtils;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.ArrayList;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+
+/**
+ * Handles notifying the user of any blacklisted calls or messages.
+ */
+class BlacklistCallNotifier extends CallsManagerListenerBase {
+
+    private static final boolean DEBUG = false;
+
+    private static final RelativeSizeSpan TIME_SPAN = new RelativeSizeSpan(0.7f);
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+
+    static final int BLACKLISTED_CALL_NOTIFICATION = 7;
+    static final int BLACKLISTED_MESSAGE_NOTIFICATION = 8;
+
+    // used to track blacklisted calls and messages
+    private static class BlacklistedItemInfo {
+        String number;
+        long date;
+        int matchType;
+
+        BlacklistedItemInfo(String number, long date, int matchType) {
+            this.number = number;
+            this.date = date;
+            this.matchType = matchType;
+        }
+    };
+    private ArrayList<BlacklistedItemInfo> mBlacklistedCalls =
+            new ArrayList<BlacklistedItemInfo>();
+    private ArrayList<BlacklistedItemInfo> mBlacklistedMessages =
+            new ArrayList<BlacklistedItemInfo>();
+
+    BlacklistCallNotifier(Context context) {
+        mContext = context;
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+
+    }
+
+    /* package */ void notifyBlacklistedCall(String number, long date, int matchType) {
+        notifyBlacklistedItem(number, date, matchType, BLACKLISTED_CALL_NOTIFICATION);
+    }
+
+    /* package */ void notifyBlacklistedMessage(String number, long date, int matchType) {
+        notifyBlacklistedItem(number, date, matchType, BLACKLISTED_MESSAGE_NOTIFICATION);
+    }
+
+    private void notifyBlacklistedItem(String number, long date,
+                                       int matchType, int notificationId) {
+        if (!BlacklistUtils.isBlacklistNotifyEnabled(mContext)) {
+            return;
+        }
+
+        if (DEBUG) Log.d(this, "notifyBlacklistedItem(). number: " + number + ", match type: "
+                + matchType + ", date: " + date + ", type: " + notificationId);
+
+        ArrayList<BlacklistedItemInfo> items = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? mBlacklistedCalls : mBlacklistedMessages;
+        PendingIntent clearIntent = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? createClearBlacklistedCallsIntent() : createClearBlacklistedMessagesIntent();
+        int iconDrawableResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                ? R.drawable.ic_block_contact_holo_dark : R.drawable.ic_block_message_holo_dark;
+
+        // Keep track of the call/message, keeping list sorted from newest to oldest
+        items.add(0, new BlacklistedItemInfo(number, date, matchType));
+
+        // Get the intent to open Blacklist settings if user taps on content ready
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName("com.android.settings",
+                "com.android.settings.blacklist.BlacklistSettings");
+        PendingIntent blSettingsIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        // Start building the notification
+        Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setSmallIcon(iconDrawableResId)
+                .setContentIntent(blSettingsIntent)
+                .setAutoCancel(true)
+                .setContentTitle(mContext.getString(R.string.blacklist_title))
+                .setWhen(date)
+                .setDeleteIntent(clearIntent);
+
+        // Add the 'Remove block' notification action only for MATCH_LIST items since
+        // MATCH_REGEX and MATCH_PRIVATE items does not have an associated specific number
+        // to unblock, and MATCH_UNKNOWN unblock for a single number does not make sense.
+        boolean addUnblockAction = true;
+
+        if (items.size() == 1) {
+            int messageResId;
+
+            switch (matchType) {
+                case BlacklistUtils.MATCH_PRIVATE:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification_private_number
+                            : R.string.blacklist_message_notification_private_number;
+                    break;
+                case BlacklistUtils.MATCH_UNKNOWN:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification_unknown_number
+                            : R.string.blacklist_message_notification_unknown_number;
+                    break;
+                default:
+                    messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                            ? R.string.blacklist_call_notification
+                            : R.string.blacklist_message_notification;
+                    break;
+            }
+            builder.setContentText(mContext.getString(messageResId, number));
+
+            if (matchType != BlacklistUtils.MATCH_LIST) {
+                addUnblockAction = false;
+            }
+        } else {
+            int messageResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? R.string.blacklist_call_notification_multiple
+                    : R.string.blacklist_message_notification_multiple;
+            String message = mContext.getString(messageResId, items.size());
+
+            builder.setContentText(message);
+            builder.setNumber(items.size());
+
+            Notification.InboxStyle style = new Notification.InboxStyle(builder);
+
+            for (BlacklistedItemInfo info : items) {
+                // Takes care of displaying "Private" instead of an empty string
+                String numberString = TextUtils.isEmpty(info.number)
+                        ? mContext.getString(R.string.blacklist_notification_list_private)
+                        : info.number;
+                style.addLine(formatSingleCallLine(numberString, info.date));
+
+                if (!TextUtils.equals(number, info.number)) {
+                    addUnblockAction = false;
+                } else if (info.matchType != BlacklistUtils.MATCH_LIST) {
+                    addUnblockAction = false;
+                }
+            }
+            style.setBigContentTitle(message);
+            style.setSummaryText(" ");
+            builder.setStyle(style);
+        }
+
+        if (addUnblockAction) {
+            int actionDrawableResId = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? R.drawable.ic_unblock_contact_holo_dark
+                    : R.drawable.ic_unblock_message_holo_dark;
+            int unblockType = notificationId == BLACKLISTED_CALL_NOTIFICATION
+                    ? BlacklistUtils.BLOCK_CALLS : BlacklistUtils.BLOCK_MESSAGES;
+            PendingIntent action = getUnblockNumberFromNotificationPendingIntent(
+                    mContext, number, unblockType);
+
+            builder.addAction(actionDrawableResId,
+                    mContext.getString(R.string.unblock_number), action);
+        }
+
+        mNotificationManager.notify(notificationId, builder.getNotification());
+    }
+
+    private PendingIntent createClearBlacklistedCallsIntent() {
+        Intent intent = new Intent(mContext, TelecomBroadcastReceiver.class);
+        intent.setAction(TelecomBroadcastIntentProcessor.ACTION_CLEAR_BLACKLISTED_CALLS);
+        return PendingIntent.getService(mContext, 0, intent, 0);
+    }
+
+    private PendingIntent createClearBlacklistedMessagesIntent() {
+        Intent intent = new Intent(mContext, TelecomBroadcastReceiver.class);
+        intent.setAction(TelecomBroadcastIntentProcessor.ACTION_CLEAR_BLACKLISTED_MESSAGES);
+        return PendingIntent.getService(mContext, 0, intent, 0);
+    }
+
+    void cancelBlacklistedNotification(int type) {
+        if ((type & BlacklistUtils.BLOCK_CALLS) != 0) {
+            mBlacklistedCalls.clear();
+            mNotificationManager.cancel(BLACKLISTED_CALL_NOTIFICATION);
+        }
+        if ((type & BlacklistUtils.BLOCK_MESSAGES) != 0) {
+            mBlacklistedMessages.clear();
+            mNotificationManager.cancel(BLACKLISTED_MESSAGE_NOTIFICATION);
+        }
+    }
+
+    private CharSequence formatSingleCallLine(String caller, long date) {
+        int flags = DateUtils.FORMAT_SHOW_TIME;
+        if (!DateUtils.isToday(date)) {
+            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
+        }
+
+        SpannableStringBuilder lineBuilder = new SpannableStringBuilder();
+        lineBuilder.append(caller);
+        lineBuilder.append("  ");
+
+        int timeIndex = lineBuilder.length();
+        lineBuilder.append(DateUtils.formatDateTime(mContext, date, flags));
+        lineBuilder.setSpan(TIME_SPAN, timeIndex, lineBuilder.length(), 0);
+
+        return lineBuilder;
+    }
+
+    /* package */ static PendingIntent getUnblockNumberFromNotificationPendingIntent(
+            Context context, String number, int type) {
+        Intent intent = new Intent(TelecomBroadcastIntentProcessor.REMOVE_BLACKLIST);
+        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_NUMBER, number);
+        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_FROM_NOTIFICATION, true);
+        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_TYPE, type);
+        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+}
diff --git a/src/com/android/server/telecom/BluetoothManager.java b/src/com/android/server/telecom/BluetoothManager.java
index aec0d3e..153883d 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -60,21 +60,23 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-
+            int bluetoothState = 0;
             if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                                          BluetoothHeadset.STATE_DISCONNECTED);
+                bluetoothState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                         BluetoothHeadset.STATE_DISCONNECTED);
                 Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
-                updateBluetoothState();
+                Log.d(this, "==> new state: %s ", bluetoothState);
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                int bluetoothHeadsetAudioState =
-                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                bluetoothState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                       BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                 Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
-                updateBluetoothState();
+                Log.d(this, "==> new state: %s", bluetoothState);
             }
+            if (bluetoothState == BluetoothHeadset.STATE_DISCONNECTED ||
+                    bluetoothState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mBluetoothConnectionPending = false;
+            }
+            updateBluetoothState();
         }
     };
 
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 9deffbe..eda75ac 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,23 +33,36 @@
 import android.os.RemoteException;
 import android.telecom.Connection;
 import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
 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.
  */
 public final class BluetoothPhoneServiceImpl {
 
+    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
@@ -74,6 +90,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
@@ -152,14 +171,30 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "getNetworkOperator");
+                    String label = null;
                     PhoneAccount account = getBestPhoneAccount();
                     if (account != null) {
-                        return account.getLabel().toString();
+                        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.getDefaultVoiceSubId();
+                            }
+                            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);
                 }
@@ -225,7 +260,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);
@@ -274,17 +319,41 @@
     private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
         @Override
         public void onCallAdded(Call call) {
-            updateHeadsetWithCallState(false /* force */);
+            Log.d(TAG, "onCallAdded");
+            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");
             mClccIndexMap.remove(call);
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
 
         @Override
         public void onCallStateChanged(Call call, int oldState, int newState) {
+            Log.d(TAG, "onCallStateChanged, call: " + call + " oldState: " + oldState +
+                    " newState: " + newState);
+            // 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 (call.mIsActiveSub) {
+                    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.
@@ -298,18 +367,25 @@
                 }
             }
 
-            // 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) {
-                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) {
+                if (!isDsdaEnabled()) {
+                    return;
+                } else if (anyActiveCall.mIsActiveSub) {
+                    Log.d(TAG, "Dialing attempted on active sub when call is active");
+                    return;
+                }
             }
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
 
         @Override
         public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
+            Log.d(TAG, "onForegroundCallChanged");
             // The BluetoothPhoneService does not need to respond to changes in foreground calls,
             // which are always accompanied by call state changes anyway.
         }
@@ -329,6 +405,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.
@@ -342,7 +419,7 @@
                 Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
                 return;
             }
-            updateHeadsetWithCallState(false /* force */);
+            updateHeadsetWithCallState(false /* force */, call);
         }
     };
 
@@ -427,11 +504,60 @@
         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);
     }
 
+    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();
@@ -461,7 +587,7 @@
             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, ringingCall.getVideoState());
@@ -506,7 +632,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();
@@ -587,6 +721,153 @@
         }
     }
 
+    /**
+     * 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 = mTelecomManager.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) &&
+                                activeChild.mIsActiveSub) {
+                            isActive = true;
+                        }
+                        state = CALL_STATE_ACTIVE;
+                    } else {
+                        Log.i(this, "this is not active child");
+                        if ((mBluetoothDsda.hasCallsOnBothSubs() == true) && call.mIsActiveSub) {
+                            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) && call.mIsActiveSub
+                && (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) {
@@ -613,90 +894,120 @@
         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) {
-        CallsManager callsManager = mCallsManager;
-        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;
+
+                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, " +
@@ -705,40 +1016,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.getDefaultVoiceSubId();
+                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.getDefaultVoiceSubId();
+                        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.getDefaultVoiceSubId();
+                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.getDefaultVoiceSubId();
+                        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();
@@ -762,6 +1189,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:
@@ -824,4 +1260,190 @@
         }
         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 boolean processDsdaChld(int chld) throws  RemoteException {
+        boolean status = true;
+        CallsManager callsManager = mCallsManager;
+        Log.i(TAG, "processDsdaChld: " + chld );
+        int activeSub = mTelecomManager.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 = mTelecomManager.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 6c923f2..e72c32c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -40,6 +40,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.ConfigResourceUtil;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telephony.CallerInfo;
@@ -319,6 +320,7 @@
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+    boolean mIsActiveSub = false;
 
     private boolean mWasConferencePreviouslyMerged = false;
 
@@ -330,6 +332,8 @@
 
     private boolean mIsLocallyDisconnecting = false;
 
+    private ConfigResourceUtil mConfigResUtil = new ConfigResourceUtil();
+
     /**
      * Persists the specified parameters and initializes the new instance.
      *
@@ -434,9 +438,8 @@
             component = mConnectionService.getComponentName().flattenToShortString();
         }
 
-
-
-        return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s]]",
+        return String.format(Locale.US,
+                "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s], %b, %s]",
                 System.identityHashCode(this),
                 CallState.toString(mState),
                 component,
@@ -444,7 +447,8 @@
                 getVideoStateDescription(getVideoState()),
                 getChildCalls().size(),
                 getParentCall() != null,
-                Connection.capabilitiesToString(getConnectionCapabilities()));
+                Connection.capabilitiesToString(getConnectionCapabilities()),
+                mIsActiveSub, mTargetPhoneAccountHandle);
     }
 
     /**
@@ -784,6 +788,10 @@
         return mConnectTimeMillis;
     }
 
+    public void setConnectTimeMillis(long connectTimeMillis) {
+        mConnectTimeMillis = connectTimeMillis;
+    }
+
     int getConnectionCapabilities() {
         return mConnectionCapabilities;
     }
@@ -1115,6 +1123,12 @@
         }
     }
 
+    void setLocalCallHold(boolean lchState) {
+        Preconditions.checkNotNull(mConnectionService);
+
+        mConnectionService.setLocalCallHold(this, lchState);
+    }
+
     /** Checks if this is a live call or not. */
     boolean isAlive() {
         switch (mState) {
@@ -1199,6 +1213,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);
+        }
+    }
+
     void mergeConference() {
         if (mConnectionService == null) {
             Log.w(this, "merging conference calls without a connection service.");
@@ -1330,6 +1352,12 @@
             return false;
         }
 
+        if (!mConfigResUtil.getBooleanValue(mContext,
+                        "config_reject_call_via_sms_enabled")) {
+            //"Respond via SMS" feature is disabled by the above config.
+            return false;
+        }
+
         if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
             // The incoming number is actually a URI (i.e. a SIP address),
             // not a regular PSTN phone number, and we can't send SMSes to
@@ -1601,7 +1629,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 938e874..86129ce 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -206,14 +206,8 @@
         Log.v(this, "onIncomingCallAnswered");
         int route = mCallAudioState.getRoute();
 
-        // We do two things:
-        // (1) If this is the first call, then we can to turn on bluetooth if available.
-        // (2) Unmute the audio for the new incoming call.
-        boolean isOnlyCall = mCallsManager.getCalls().size() == 1;
-        if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
-            mBluetoothManager.connectBluetoothAudio();
-            route = CallAudioState.ROUTE_BLUETOOTH;
-        }
+        // BT stack will connect audio upon receiving active call state.
+        // We unmute the audio for the new incoming call.
 
         setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask());
 
@@ -428,10 +422,16 @@
     }
 
     private void onCallUpdated(Call call) {
-        updateAudioStreamAndMode(call);
-        if (call != null && call.getState() == CallState.ACTIVE &&
+
+        if (call != null) {
+            if (call.getState() != CallState.DISCONNECTED) {
+                updateAudioStreamAndMode(call);
+            }
+
+            if (call.getState() == CallState.ACTIVE &&
                             call == mCallToSpeedUpMTAudio) {
-            mCallToSpeedUpMTAudio = null;
+                mCallToSpeedUpMTAudio = null;
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index a6840b9..3913de6 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;
@@ -17,6 +18,8 @@
 import android.telephony.PhoneNumberUtils;
 import android.widget.Toast;
 
+import org.codeaurora.QtiVideoCallConstants;
+
 /**
  * Single point of entry for all outgoing and incoming calls.
  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -64,15 +67,25 @@
             Context context,
             CallsManager callsManager,
             Intent intent) {
-        if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) {
-            return;
-        }
-
         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);
         }
@@ -80,13 +93,42 @@
         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);
+        }
+
+        final int callDomain = intent.getIntExtra(
+                QtiVideoCallConstants.EXTRA_CALL_DOMAIN, QtiVideoCallConstants.DOMAIN_AUTOMATIC);
+        Log.d(CallIntentProcessor.class, "callDomain = " + callDomain);
+        clientExtras.putInt(QtiVideoCallConstants.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);
+
+        boolean isCallPull = intent.getBooleanExtra(TelephonyProperties.EXTRA_IS_CALL_PULL, false);
+        Log.d(CallIntentProcessor.class, "processOutgoingCallIntent callPull = " + isCallPull);
+        if (isCallPull) {
+            clientExtras.putBoolean(TelephonyProperties.EXTRA_IS_CALL_PULL, isCallPull);
+        }
+
+        Log.i(CallIntentProcessor.class, " processOutgoingCallIntent handle = " + handle
+                + ",scheme = " + scheme + ", uriString = " + uriString
+                + ", isSkipSchemaParsing = " + isSkipSchemaParsing
+                + ", isAddParticipant = " + isAddParticipant
+                + ", isCallPull = " + isCallPull);
 
         // Ensure call subject is passed on to the connection service.
         if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 195bd7c..d027086 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.CallLog.Calls;
@@ -40,8 +41,11 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.util.BlacklistUtils;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.Collection;
@@ -81,6 +85,7 @@
         void onVideoStateChanged(Call call);
         void onCanAddCallChanged(boolean canAddCall);
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+        void onMergeFailed(Call call);
     }
 
     private static final String TAG = "CallsManager";
@@ -91,12 +96,16 @@
     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};
 
     private static final int[] LIVE_CALL_STATES =
-            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.ACTIVE};
+            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
+            CallState.ACTIVE};
 
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
@@ -133,6 +142,7 @@
     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
+    private final BlacklistCallNotifier mBlacklistCallNotifier;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
     /* Handler tied to thread in which CallManager was initialized. */
@@ -146,6 +156,21 @@
      */
     private Call mForegroundCall;
 
+    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 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 InCallTonePlayer.Factory mPlayerFactory;
+    private String mLchSub = null;
+
+    private InCallTonePlayer mLocalCallReminderTonePlayer = null;
+    private InCallTonePlayer mSupervisoryCallHoldTonePlayer = null;
+    private String mSubInConversation = null;
+
     private Runnable mStopTone;
 
     /**
@@ -160,19 +185,22 @@
             PhoneAccountRegistrar phoneAccountRegistrar,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
             ProximitySensorManagerFactory proximitySensorManagerFactory,
-            InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+            InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+            BlacklistCallNotifier blacklistCallNotifier) {
         mContext = context;
         mLock = lock;
         mContactsAsyncHelper = contactsAsyncHelper;
         mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
+        mBlacklistCallNotifier = blacklistCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = new WiredHeadsetManager(context);
         mDockManager = new DockManager(context);
         mCallAudioManager = new CallAudioManager(
                 context, mLock, statusBarNotifier, mWiredHeadsetManager, mDockManager, this);
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock);
+        mPlayerFactory = playerFactory;
         mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
@@ -243,16 +271,27 @@
     @Override
     public void onSuccessfulIncomingCall(Call incomingCall) {
         Log.d(this, "onSuccessfulIncomingCall");
-        setCallState(incomingCall, CallState.RINGING, "successful incoming call");
 
-        if (hasMaximumRingingCalls() || hasMaximumDialingCalls()) {
-            incomingCall.reject(false, null);
-            // since the call was not added to the list of calls, we have to call the missed
-            // call notifier and the call logger manually.
-            mMissedCallNotifier.showMissedCallNotification(incomingCall);
-            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
+        if (isCallBlacklisted(incomingCall)) {
+            mCallLogManager.logCall(incomingCall, Calls.BLACKLIST_TYPE);
+            incomingCall.setDisconnectCause(
+                    new DisconnectCause(android.telephony.DisconnectCause.CALL_BLACKLISTED));
         } else {
-            addCall(incomingCall);
+            setCallState(incomingCall, CallState.RINGING, "ringing set explicitly");
+            if (hasMaximumRingingCalls(incomingCall.getTargetPhoneAccount().getId()) || hasMaximumDialingCalls()) {
+                incomingCall.reject(false, null);
+                // since the call was not added to the list of calls, we have to call the missed
+                // call notifier and the call logger manually.
+                mMissedCallNotifier.showMissedCallNotification(incomingCall);
+                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
+            } else {
+                if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                    == TelephonyManager.MultiSimVariants.DSDA) {
+                    incomingCall.mIsActiveSub = true;
+                }
+                addCall(incomingCall);
+                setActiveSubscription(incomingCall.getTargetPhoneAccount().getId());
+            }
         }
     }
 
@@ -595,12 +634,40 @@
     Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
         Call call = getNewOutgoingCall(handle);
 
+        if (extras!=null) {
+            call.setVideoState(extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_AUDIO_ONLY));
+        }
+
+        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 =
-                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false);
+                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(scheme, false);
 
         Log.v(this, "startOutgoingCall found accounts = " + accounts);
 
-        if (mForegroundCall != null) {
+        if (mForegroundCall != null && TelephonyManager.getDefault().getMultiSimConfiguration()
+                != TelephonyManager.MultiSimVariants.DSDA) {
             Call ongoingCall = mForegroundCall;
             // If there is an ongoing call, use the same phone account to place this new call.
             // If the ongoing call is a conference call, we fetch the phone account from the
@@ -628,7 +695,7 @@
             // No preset account, check if default exists that supports the URI scheme for the
             // handle.
             phoneAccountHandle =
-                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme());
+                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(scheme);
         }
 
         call.setTargetPhoneAccount(phoneAccountHandle);
@@ -705,21 +772,21 @@
 
         call.setHandle(uriHandle);
         call.setGatewayInfo(gatewayInfo);
+        // Auto-enable speakerphone if the originating intent specified to do so, or if the call
+        // is a video call or if the phone is docked.
+        call.setStartWithSpeakerphoneOn(speakerphoneOn || isSpeakerphoneAutoEnabled(videoState)
+                || mDockManager.isDocked());
         call.setVideoState(videoState);
 
-        if (speakerphoneOn) {
-            Log.i(this, "%s Starting with speakerphone as requested", call);
-        } else {
-            Log.i(this, "%s Starting with speakerphone because car is docked.", call);
-        }
-        call.setStartWithSpeakerphoneOn(speakerphoneOn || mDockManager.isDocked());
-
         if (call.isEmergencyCall()) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
             call.setTargetPhoneAccount(null);
         }
 
         if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
+            if (!call.isEmergencyCall()) {
+                updateLchStatus(call.getTargetPhoneAccount().getId());
+            }
             // 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);
@@ -727,6 +794,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.
@@ -748,18 +832,20 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
+            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 (mForegroundCall != null && mForegroundCall != call &&
-                    (mForegroundCall.isActive() ||
-                     mForegroundCall.getState() == CallState.DIALING)) {
+            if (activeCall != null && activeCall != call &&
+                    (activeCall.isActive() ||
+                     activeCall.getState() == CallState.DIALING)) {
                 if (0 == (mForegroundCall.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 (mForegroundCall.getConnectionService() != call.getConnectionService()) {
-                        mForegroundCall.disconnect();
+                    if (activeCall.getConnectionService() != call.getConnectionService()) {
+                        activeCall.disconnect();
                     }
                 } else {
                     Call heldCall = getHeldCall();
@@ -771,7 +857,7 @@
 
                     Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
                             mForegroundCall, call);
-                    mForegroundCall.hold();
+                    activeCall.hold();
                 }
                 // TODO: Wait until we get confirmation of the active call being
                 // on-hold before answering the new call.
@@ -781,19 +867,36 @@
             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);
-            if (VideoProfile.isVideo(videoState) &&
-                !mWiredHeadsetManager.isPluggedIn() &&
-                !mCallAudioManager.isBluetoothDeviceAvailable() &&
-                isSpeakerEnabledForVideoCalls()) {
+            if (isSpeakerphoneAutoEnabled(videoState)) {
                 call.setStartWithSpeakerphoneOn(true);
             }
         }
     }
 
+    /**
+     * Determines if the speakerphone should be automatically enabled for the call.  Speakerphone
+     * should be enabled if the call is a video call and bluetooth or the wired headset are not in
+     * use.
+     *
+     * @param videoState The video state of the call.
+     * @return {@code true} if the speakerphone should be enabled.
+     */
+    private boolean isSpeakerphoneAutoEnabled(int videoState) {
+        return VideoProfile.isVideo(videoState) &&
+            !mWiredHeadsetManager.isPluggedIn() &&
+            !mCallAudioManager.isBluetoothDeviceAvailable() &&
+            isSpeakerEnabledForVideoCalls();
+    }
+
+    /**
+     * Determines if the speakerphone should be automatically enabled for video calls.
+     *
+     * @return {@code true} if the speakerphone should automatically be enabled.
+     */
     private static boolean isSpeakerEnabledForVideoCalls() {
         return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
                 PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
@@ -812,6 +915,7 @@
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
             }
+            setActiveSubscription(getConversationSub());
             call.reject(rejectWithMessage, textMessage);
         }
     }
@@ -906,8 +1010,13 @@
         } else {
             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()))) {
                     c.hold();
                 }
             }
@@ -915,6 +1024,16 @@
         }
     }
 
+    /**
+     *  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("sip") || id2.contains("sip")));
+        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) {
         mCallAudioManager.mute(shouldMute);
@@ -952,6 +1071,8 @@
             // respecting a rewritten number or a canceled number. This is unlikely since
             // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
             // a phone account from the in-call UI.
+            Log.i(this, "phoneAccountSelected , id = %s", account.getId());
+            updateLchStatus(account.getId());
             call.setTargetPhoneAccount(account);
 
             // Note: emergency calls never go through account selection dialog so they never
@@ -982,7 +1103,9 @@
 
     void markCallAsDialing(Call call) {
         setCallState(call, CallState.DIALING, "dialing set explicitly");
+        maybeMoveToEarpiece(call);
         maybeMoveToSpeakerPhone(call);
+        setActiveSubscription(call.getTargetPhoneAccount().getId());
     }
 
     void markCallAsActive(Call call) {
@@ -1001,8 +1124,59 @@
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
     void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        // Show MT call in call log as a missed call if immediately disconnected
+        // before creating a connection.
+        if (!mCalls.contains(call) && (DisconnectCause.MISSED == disconnectCause.getCode())) {
+            addCall(call);
+            mMissedCallNotifier.showMissedCallNotification(call);
+        }
         call.setDisconnectCause(disconnectCause);
+        int prevState = call.getState();
         setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
+        String activeSub = getActiveSubscription();
+        String conversationSub = getConversationSub();
+        String lchSub = IsAnySubInLch();
+
+        PhoneAccount phAcc =
+                 getPhoneAccountRegistrar().getPhoneAccount(call.getTargetPhoneAccount());
+        if (activeSub != null && (call.getTargetPhoneAccount() != null &&
+                    call.getTargetPhoneAccount().getId().equals(activeSub)) &&
+                    (phAcc != null) && (phAcc.isSet(PhoneAccount.LCH)) &&
+                    (conversationSub != null) &&
+                    (!conversationSub.equals(activeSub))) {
+            Log.d(this,"Set active sub to conversation sub");
+            setActiveSubscription(conversationSub);
+        } else if ((conversationSub == 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);
+        }
+
+        if ((call.getTargetPhoneAccount() != null) && (phAcc != null) &&
+                (phAcc.isSet(PhoneAccount.LCH))) {
+            Call activecall = getFirstCallWithState(call.getTargetPhoneAccount().getId(),
+                    CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
+            Log.d(this,"activecall: " + activecall);
+            if (activecall == null) {
+                phAcc.unSetBit(PhoneAccount.LCH);
+                manageDsdaInCallTones(false);
+            }
+        }
+    }
+
+    private String IsAnySubInLch() {
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
+            if ((phAcc != null) && (phAcc.isSet(PhoneAccount.LCH))) {
+                Log.d(this, "Sub in LCH: " + ph.getId());
+                return ph.getId();
+            }
+        }
+        return null;
     }
 
     /**
@@ -1010,6 +1184,11 @@
      */
     void markCallAsRemoved(Call call) {
         removeCall(call);
+        if (!hasAnyCalls()) {
+            updateLchStatus(null);
+            setActiveSubscription(null);
+            manageDsdaInCallTones(false);
+        }
         if (mLocallyDisconnectingCalls.contains(call)) {
             mLocallyDisconnectingCalls.remove(call);
             if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
@@ -1045,6 +1224,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;
     }
@@ -1103,7 +1286,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;
             }
         }
@@ -1142,7 +1330,7 @@
     }
 
     Call getFirstCallWithState(int... states) {
-        return getFirstCallWithState(null, states);
+        return getFirstCallWithState((Call) null, states);
     }
 
     /**
@@ -1177,6 +1365,42 @@
         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
+            if (mForegroundCall != null && mForegroundCall.getState() == currentState
+                    && mForegroundCall.getTargetPhoneAccount() != null
+                    && isSamePhAccIdOrSipId(mForegroundCall.getTargetPhoneAccount().getId(), subId)) {
+                return mForegroundCall;
+            }
+
+            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(
             PhoneAccountHandle phoneAccount,
             ParcelableConference parcelableConference) {
@@ -1242,6 +1466,14 @@
     }
 
     /**
+     * Retrieves the {@link MissedCallNotifier}
+     * @return The {@link MissedCallNotifier}.
+     */
+    BlacklistCallNotifier getBlacklistCallNotifier() {
+        return mBlacklistCallNotifier;
+    }
+
+    /**
      * Adds the specified call to the main list of live calls.
      *
      * @param call The call to add.
@@ -1338,6 +1570,7 @@
             }
             Trace.endSection();
         }
+        manageDsdaInCallTones(false);
     }
 
     /**
@@ -1346,26 +1579,61 @@
     private void updateForegroundCall() {
         Trace.beginSection("updateForegroundCall");
         Call newForegroundCall = null;
-        for (Call call : mCalls) {
             // TODO: Foreground-ness needs to be explicitly set. No call, regardless
             // of its state will be foreground by default and instead the connection service should
             // be notified when its calls enter and exit foreground state. Foreground will mean that
             // the call should play audio and listen to microphone if it wants.
 
-            // Only top-level calls can be in foreground
-            if (call.getParentCall() != null) {
-                continue;
-            }
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                == TelephonyManager.MultiSimVariants.DSDA) {
+            String lchSub = getLchSub();
+            for (Call call : mCalls) {
+                // Only top-level calls can be in foreground
+                if (call.getParentCall() != null) {
+                    continue;
+                }
 
-            // Active calls have priority.
-            if (call.isActive()) {
-                newForegroundCall = call;
-                break;
-            }
+                PhoneAccountHandle ph = call.getTargetPhoneAccount();
+                if (ph != null && ph.getId().equals(lchSub)) continue;
+                // Active calls have priority.
+                if (call.isActive()) {
+                    newForegroundCall = call;
+                    break;
+                }
 
-            if (call.isAlive() || call.getState() == CallState.RINGING) {
-                newForegroundCall = call;
-                // Don't break in case there's an active call that has priority.
+                if ((call.isAlive() && call.getState() != CallState.ON_HOLD)
+                     || call.getState() == CallState.RINGING) {
+                    newForegroundCall = call;
+                    // Don't break in case there's an active call that has priority.
+                }
+            }
+            // if active sub doesn't have any calls, then consider calls on all subs,
+            // which ever call is active set that as foreground call. give more priority
+            // to ringing call on LCH sub over active call.
+            if (newForegroundCall == null) {
+                newForegroundCall = getFirstCallWithState(CallState.RINGING);
+                if (newForegroundCall == null) {
+                    newForegroundCall = getFirstCallWithState(CallState.ACTIVE);
+                }
+            }
+        } else {
+            for (Call call : mCalls) {
+                // Only top-level calls can be in foreground
+                if (call.getParentCall() != null) {
+                    continue;
+                }
+
+                // Active calls have priority.
+                if (call.isActive()) {
+                    newForegroundCall = call;
+                    break;
+                }
+
+                if ((call.isAlive() && call.getState() != CallState.ON_HOLD)
+                     || call.getState() == CallState.RINGING) {
+                    newForegroundCall = call;
+                    // Don't break in case there's an active call that has priority.
+                }
             }
         }
 
@@ -1452,18 +1720,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);
     }
@@ -1473,6 +1767,10 @@
     }
 
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                == TelephonyManager.MultiSimVariants.DSDA) {
+            return makeRoomForOutgoingCallForDsda(call, isEmergency);
+        }
         if (hasMaximumLiveCalls()) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
             // have to change.
@@ -1563,6 +1861,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.
      *
@@ -1589,6 +1925,13 @@
         }
     }
 
+    private void maybeMoveToEarpiece(Call call) {
+        if (!call.getStartWithSpeakerphoneOn() && !mWiredHeadsetManager.isPluggedIn() &&
+                !mCallAudioManager.isBluetoothDeviceAvailable()) {
+            setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+        }
+    }
+
     /**
      * Creates a new call for an existing connection.
      *
@@ -1669,4 +2012,344 @@
             pw.decreaseIndent();
         }
     }
+
+    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;
+            }
+        }
+    }
+
+    public void switchToOtherActiveSub(String subId) {
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                != TelephonyManager.MultiSimVariants.DSDA) {
+            return;
+        }
+        Log.d(this, "switchToOtherActiveSub sub:" + subId);
+        setActiveSubscription(subId);
+        updateLchStatus(subId);
+        manageDsdaInCallTones(true);
+        updateForegroundCall();
+    }
+
+    public synchronized void setActiveSubscription(String subId) {
+        Log.d(this, "setActiveSubscription = " + subId);
+        if (TelephonyManager.getDefault().getMultiSimConfiguration()
+                != TelephonyManager.MultiSimVariants.DSDA) {
+            return;
+        }
+        boolean changed = false;
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
+            if (subId != null && subId.equals(ph.getId())
+                    && (phAcc != null) && !phAcc.isSet(PhoneAccount.ACTIVE)) {
+                changed = true;
+                phAcc.setBit(PhoneAccount.ACTIVE);
+            } else if (subId != null && !subId.equals(ph.getId())
+                    && (phAcc != null) && phAcc.isSet(PhoneAccount.ACTIVE)) {
+                changed = true;
+                phAcc.unSetBit(PhoneAccount.ACTIVE);
+            } else if ((subId == null) && (phAcc != null) && phAcc.isSet(PhoneAccount.ACTIVE)) {
+                phAcc.unSetBit(PhoneAccount.ACTIVE);
+            }
+        }
+        if (!changed) {
+            Log.d(this, "setActiveSubscription not changed ");
+            return;
+        } else {
+            Log.d(this, "setActiveSubscription changed " );
+            for (Call call : mCalls) {
+                PhoneAccountHandle ph = call.getTargetPhoneAccount();
+                if (ph != null) {
+                    call.mIsActiveSub = ph.getId().equals(subId) ? true : false;
+                }
+                for (CallsManagerListener listener : mListeners) {
+                    listener.onCallStateChanged(call, call.getState(), call.getState());
+                }
+            }
+        }
+    }
+
+    public String getActiveSubscription() {
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            if (getPhoneAccountRegistrar()
+                    .getPhoneAccount(ph).isSet(PhoneAccount.ACTIVE)) {
+                Log.d(this, "getActiveSubscription: " + ph.getId());
+                return ph.getId();
+            }
+        }
+        return null;
+    }
+
+    private String getConversationSub() {
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
+            if (phAcc != null && !phAcc.isSet(PhoneAccount.LCH) &&
+                    (getFirstCallWithState(ph.getId(), CallState.ACTIVE, CallState.DIALING,
+                        CallState.ON_HOLD) != null)) {
+                Log.d(this, "getConversationSub: " + ph.getId());
+                return ph.getId();
+            }
+        }
+        return null;
+    }
+
+    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
+            if (mForegroundCall != null && mForegroundCall.getState() == currentState
+                    && (mForegroundCall.getTargetPhoneAccount() != null)
+                    && isSamePhAccIdOrSipId(mForegroundCall.getTargetPhoneAccount().getId(),
+                    sub)) {
+                return mForegroundCall;
+            }
+
+            for (Call call : mCalls) {
+                if ((currentState == call.getState()) &&
+                        (call.getTargetPhoneAccount() != null) &&
+                        (isSamePhAccIdOrSipId(call.getTargetPhoneAccount().getId(), sub))) {
+                    return call;
+                }
+            }
+        }
+        return null;
+    }
+
+    private String getLchSub() {
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
+            if (phAcc != null && phAcc.isSet(PhoneAccount.LCH)) {
+                return ph.getId();
+            }
+        }
+        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("inband")) {
+            // if "persist.radio.sch_tone" is set to "inband", play inband supervisory
+            // call hold tone. if set to "dtmf", play the SCH tones
+            // over DTMF, don't play SCH tones for anyother value.
+            if (mSupervisoryCallHoldTonePlayer == null) {
+                Log.d(this, " startDsdaInCallTones: Supervisory call hold tone ");
+                mSupervisoryCallHoldTonePlayer =
+                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_SUPERVISORY_CH);
+                mSupervisoryCallHoldTonePlayer.start();
+            }
+        } else 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 (mSupervisoryCallHoldTonePlayer != null) {
+            Log.d(this, " stopMSimInCallTones: Supervisory call hold tone ");
+            mSupervisoryCallHoldTonePlayer.stopTone();
+            mSupervisoryCallHoldTonePlayer = null;
+        }
+        if (sSupervisoryCallHoldToneConfig.equals("dtmf")) {
+            Log.d(this, " stopMSimInCallTones: stop SCH Dtmf call hold tone ");
+            stopLchDtmf();
+            /* Remove any previous dtmf nssages from queue */
+            removeAnyPendingDtmfMsgs();
+        }
+    }
+
+    /**
+     * 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 updateLchStatus(String subInConversation) {
+        Call removeFromLch = null;
+        Log.d(this, "updateLchStatus subInConversation: " + subInConversation);
+        if (subInConversation != null && subInConversation.contains("sip")) {
+            return;
+        }
+        for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
+            String sub = ph.getId();
+            if (sub != null && sub.contains("sip")) {
+                Log.d(this, "update lch. Skipping account: " + sub);
+                continue;
+            }
+            PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
+            boolean lchState = false;
+            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 ((phAcc != null) && (lchState != phAcc.isSet(PhoneAccount.LCH))) {
+                Call call = getNonRingingLiveCall(sub);
+                Log.d(this, " setLocal Call Hold to  = " + lchState + " sub:" + sub);
+
+                if (call != null) {
+                    if (call.getChildCalls().size() > 1) {
+                        // Since for a conference call corresponding telephonyconnection doesn't
+                        // exist, so send lch request on its child call
+                        call = call.getChildCalls().get(0);
+                    }
+                    if (lchState) {
+                        call.setLocalCallHold(true);
+                    } else {
+                        removeFromLch = call;
+                    }
+                }
+                if (lchState) {
+                    phAcc.setBit(PhoneAccount.LCH);
+                } else {
+                    phAcc.unSetBit(PhoneAccount.LCH);
+                }
+            }
+        }
+        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.
+            removeFromLch.setLocalCallHold(false);
+        }
+    }
+
+    void resetCdmaConnectionTime(Call call) {
+        call.setConnectTimeMillis(System.currentTimeMillis());
+        if (mCalls.contains(call)) {
+            for (CallsManagerListener listener : mListeners) {
+                listener.onCallStateChanged(call, CallState.ACTIVE, CallState.ACTIVE);
+            }
+            updateForegroundCall();
+        }
+    }
+
+    void onMergeFailed(Call call) {
+        if (mCalls.contains(call)) {
+            for (CallsManagerListener listener : mListeners) {
+                listener.onMergeFailed(call);
+            }
+        }
+    }
+
+    protected boolean isCallBlacklisted(Call c) {
+        final String number = c.getCallerInfo().phoneNumber;
+        // See if the number is in the blacklist
+        // Result is one of: MATCH_NONE, MATCH_LIST or MATCH_REGEX
+        int listType = BlacklistUtils.isListed(mContext, number, BlacklistUtils.BLOCK_CALLS);
+        if (listType != BlacklistUtils.MATCH_NONE) {
+            // We have a match, set the user and hang up the call and notify
+            Log.d(this, "Incoming call from " + number + " blocked.");
+            mBlacklistCallNotifier.notifyBlacklistedCall(number,
+                    c.getCreationTimeMillis(), listType);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 58085a0..773838f 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -84,4 +84,8 @@
     public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
 
     }
+
+    @Override
+    public void onMergeFailed(Call call) {
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index d5ab5cf..ff47c66 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -122,6 +122,27 @@
         }
 
         @Override
+        public void resetCdmaConnectionTime(String callId) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("resetCdmaConnectionTime %s", callId);
+                    if (mCallIdMapper.isValidCallId(callId) ||
+                            mCallIdMapper.isValidConferenceId(callId)) {
+                        Call call = mCallIdMapper.getCall(callId);
+                        if (call != null) {
+                            mCallsManager.resetCdmaConnectionTime(call);
+                        } else {
+                            // Log.w(this, "resetCdmaConnectionTime, unknown call id: %s", msg.obj);
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider) {
             long token = Binder.clearCallingIdentity();
             try {
@@ -282,6 +303,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 {
@@ -313,7 +338,7 @@
                             // the failure event all the way to InCallUI instead of stopping
                             // it here. That way we can also handle the UI of notifying that
                             // the merged has failed.
-                            call.setConnectionCapabilities(call.getConnectionCapabilities(), true);
+                            mCallsManager.onMergeFailed(call);
                         } else {
                             Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
                         }
@@ -496,7 +521,14 @@
                     if (mCallIdMapper.isValidCallId(callId)
                             || mCallIdMapper.isValidConferenceId(callId)) {
                         Call call = mCallIdMapper.getCall(callId);
-                        if (call != null) {
+                        if (call != null && extras != null) {
+                            if (extras.getParcelable(EMR_DIAL_ACCOUNT) instanceof
+                                    PhoneAccountHandle) {
+                                PhoneAccountHandle account = extras.
+                                        getParcelable(EMR_DIAL_ACCOUNT);
+                                Log.d(this, "setTargetPhoneAccount, account = " + account);
+                                call.setTargetPhoneAccount(account);
+                            }
                             call.setExtras(extras);
                         }
                     }
@@ -599,6 +631,7 @@
     private final ConnectionServiceRepository mConnectionServiceRepository;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallsManager mCallsManager;
+    private static final String EMR_DIAL_ACCOUNT = "emr_dial_account";
 
     /**
      * Creates a connection service.
@@ -797,6 +830,18 @@
         }
     }
 
+    /** @see ConnectionService#setLocalCallHold(String,int) */
+    void setLocalCallHold(Call call, boolean lchStatus) {
+        final String callId = mCallIdMapper.getCallId(call);
+        if (callId != null && isServiceValid("SetLocalCallHold")) {
+            try {
+                logOutgoing("SetLocalCallHold %s %b", mCallIdMapper.getCallId(call), lchStatus);
+                mServiceInterface.setLocalCallHold(mCallIdMapper.getCallId(call), lchStatus);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /** @see IConnectionService#stopDtmfTone(String) */
     void stopDtmfTone(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
@@ -879,6 +924,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/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 9239288..4abcc39 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -343,4 +343,16 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    @Override
+    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 8a8e4f4..78fc1f2 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -226,6 +226,20 @@
     }
 
     @Override
+    public void onMergeFailed(Call call) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "onMergeFailed :" + call);
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.onMergeFailed(toParcelableCall(call, true));
+                } catch (RemoteException ignored) {
+                    Log.i(this, "onMergeFailed exception:" + ignored);
+                }
+            }
+        }
+    }
+
+    @Override
     public void onConnectionServiceChanged(
             Call call,
             ConnectionServiceWrapper oldService,
@@ -675,7 +689,8 @@
                 call.getVideoState(),
                 conferenceableCallIds,
                 call.getIntentExtras(),
-                call.getExtras());
+                call.getExtras(),
+                call.mIsActiveSub);
     }
 
     private static int getParcelableState(Call call) {
@@ -770,7 +785,19 @@
         android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
 
         Connection.CAPABILITY_CAN_PAUSE_VIDEO,
-        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO
+        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO,
+
+        Connection.CAPABILITY_VOICE_PRIVACY,
+        android.telecom.Call.Details.CAPABILITY_VOICE_PRIVACY,
+
+        Connection.CAPABILITY_ADD_PARTICIPANT,
+        android.telecom.Call.Details.CAPABILITY_ADD_PARTICIPANT,
+
+        Connection.CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL,
+
+        Connection.CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE
     };
 
     private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0d2e3c4..c2c6e62 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -61,6 +61,8 @@
     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;
+    public static final int TONE_SUPERVISORY_CH = 16;
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
@@ -181,6 +183,18 @@
                     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_SUPERVISORY_CH:
+                    toneType = ToneGenerator.TONE_SUPERVISORY_CH;
+                    toneVolume = RELATIVE_VOLUME_HIPRI;
+                    // Supervisory call held 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 17ccdb1..2cce673 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -32,8 +32,11 @@
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.TelephonyProperties;
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
@@ -122,11 +125,21 @@
                 return;
             }
 
-            Uri resultHandleUri = Uri.fromParts(PhoneNumberUtils.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(PhoneNumberUtils.isUriNumber(resultNumber) ?
+                        PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, resultNumber, null);
+            }
+
             if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
                 Log.v(this, "Call number unmodified after new outgoing call intent broadcast.");
             } else {
@@ -192,13 +205,20 @@
         }
 
         String number = PhoneNumberUtils.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 = PhoneNumberUtils.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 = PhoneNumberUtils.convertKeypadLettersToDigits(number);
             number = PhoneNumberUtils.stripSeparators(number);
         }
@@ -254,7 +274,11 @@
         }
 
         Log.i(this, "Sending NewOutgoingCallBroadcast for %s", mCall);
-        broadcastIntent(intent, number, !callImmediately);
+        if (isSkipSchemaParsing) {
+            broadcastIntent(intent, handle.toString(), !callImmediately);
+        } else {
+            broadcastIntent(intent, number, !callImmediately);
+        }
         return DisconnectCause.NOT_DISCONNECTED;
     }
 
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 57ae24b..541dbfc 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -58,6 +58,9 @@
     public void onCallAdded(Call call) {
         if (call.getState() == CallState.RINGING) {
             sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_RINGING);
+        } else if (call.getState() == CallState.SELECT_PHONE_ACCOUNT ||
+                call.getState() == CallState.CONNECTING) {
+            sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_OFFHOOK);
         }
     };
 
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 99fb842..c9e4fa7 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -181,7 +181,7 @@
         Call foregroundCall = mCallsManager.getForegroundCall();
         Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
 
-        if (mRingingCalls.contains(foregroundCall)) {
+        if (mRingingCalls.contains(foregroundCall) && (!mCallsManager.hasActiveOrHoldingCall())) {
             // The foreground call is one of incoming calls so play the ringer out loud.
             stopCallWaiting(call);
 
@@ -191,7 +191,7 @@
 
             AudioManager audioManager =
                     (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) >= 0) {
                 if (mState != STATE_RINGING) {
                     Log.event(call, Log.Events.START_RINGER);
                     mState = STATE_RINGING;
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 89aa2aa..a6a5796 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
+import com.android.internal.telephony.util.BlacklistUtils;
 
 public final class TelecomBroadcastIntentProcessor {
     /** The action used to send SMS response for the missed call notification. */
@@ -41,6 +42,22 @@
         mCallsManager = callsManager;
     }
 
+    public static final String ACTION_CLEAR_BLACKLISTED_CALLS =
+            "com.android.phone.intent.CLEAR_BLACKLISTED_CALLS";
+    /** This action is used to clear blacklisted messages. */
+    public static final String ACTION_CLEAR_BLACKLISTED_MESSAGES =
+            "com.android.phone.intent.CLEAR_BLACKLISTED_MESSAGES";
+
+    public static final String ACTION_REJECTED_SMS =
+            "android.provider.Telephony.SMS_REJECTED";
+
+    // For adding to Blacklist from call log
+    static final String REMOVE_BLACKLIST = "com.android.phone.REMOVE_BLACKLIST";
+    static final String EXTRA_NUMBER = "number";
+    static final String EXTRA_TYPE = "type";
+    static final String EXTRA_FROM_NOTIFICATION = "fromNotification";
+
+
     public void processIntent(Intent intent) {
         String action = intent.getAction();
 
@@ -72,6 +89,32 @@
         // Clear the missed call notification and call log entries.
         } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
             missedCallNotifier.clearMissedCalls();
+        }  else if (ACTION_CLEAR_BLACKLISTED_CALLS.equals(action)) {
+            BlacklistCallNotifier bcn = mCallsManager.getBlacklistCallNotifier();
+            bcn.cancelBlacklistedNotification(BlacklistUtils.BLOCK_CALLS);
+        } else if (ACTION_CLEAR_BLACKLISTED_MESSAGES.equals(action)) {
+            BlacklistCallNotifier bcn = mCallsManager.getBlacklistCallNotifier();
+            bcn.cancelBlacklistedNotification(BlacklistUtils.BLOCK_MESSAGES);
+        } else if (intent.getAction().equals(REMOVE_BLACKLIST)) {
+            if (intent.getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
+                // Dismiss the notification that brought us here
+                int blacklistType = intent.getIntExtra(EXTRA_TYPE, 0);
+                BlacklistCallNotifier bcn = mCallsManager.getBlacklistCallNotifier();
+                bcn.cancelBlacklistedNotification(blacklistType);
+                BlacklistUtils.addOrUpdate(mContext, intent.getStringExtra(EXTRA_NUMBER),
+                        0, blacklistType);
+            }
+        } else if (ACTION_REJECTED_SMS.equals(action)) {
+            if (!intent.getBooleanExtra("blacklisted", false)) {
+                return;
+            }
+
+            String sender = intent.getStringExtra("sender");
+            long timestamp = intent.getLongExtra("timestamp", 0);
+            int matchType = intent.getIntExtra("blacklistMatchType", -1);
+
+            BlacklistCallNotifier bcn = mCallsManager.getBlacklistCallNotifier();
+            bcn.notifyBlacklistedMessage(sender, timestamp, matchType);
         }
     }
 
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 0f0a456..95be8ff 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -724,6 +724,30 @@
         }
 
         /**
+         * @see android.telecom.TelecomManager#getActiveSubscription
+         */
+        public int getActiveSubscription() {
+            synchronized (mLock) {
+                String activeSub = mCallsManager.getActiveSubscription();
+                return (activeSub == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID:
+                        Integer.parseInt(activeSub);
+            }
+        }
+
+        /**
+         * @see android.telecom.TelecomManager#switchToOtherActiveSub
+         */
+        public void switchToOtherActiveSub(int subId) {
+            enforceModifyPermission();
+
+            synchronized (mLock) {
+                String activeSub = (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+                        ? null : String.valueOf(subId);
+                mCallsManager.switchToOtherActiveSub(activeSub);
+            }
+        }
+
+        /**
          * @see android.telecom.TelecomManager#addNewIncomingCall
          */
         @Override
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index c3ab0dc..cddf784 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -68,6 +68,12 @@
     private final TelecomServiceImpl mTelecomServiceImpl;
     private final ContactsAsyncHelper mContactsAsyncHelper;
 
+    /**
+     * Blacklist call notifier. Exists here so that the instance can be shared with
+     * {@link TelecomBroadcastReceiver}.
+     */
+    private BlacklistCallNotifier mBlacklistCallNotifier;
+
     private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -101,6 +107,7 @@
         mMissedCallNotifier = missedCallNotifier;
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
         mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
+        mBlacklistCallNotifier = new BlacklistCallNotifier(mContext);
 
         mCallsManager = new CallsManager(
                 mContext,
@@ -111,7 +118,8 @@
                 mPhoneAccountRegistrar,
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
-                inCallWakeLockControllerFactory);
+                inCallWakeLockControllerFactory,
+                mBlacklistCallNotifier);
 
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 8f451b5..4db6ba5 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -34,7 +34,6 @@
 import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.widget.Toast;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -123,7 +122,7 @@
                 VideoProfile.STATE_AUDIO_ONLY);
         Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
 
-        if (VideoProfile.isVideo(videoState)
+        if (!isEmergencyVideoCallingSupported() && VideoProfile.isVideo(videoState)
                 && TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
             Log.d(this, "Emergency call...Converting video call to voice...");
             videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -131,9 +130,11 @@
                     videoState);
         }
 
-        if (VideoProfile.isVideo(videoState) && isTtyModeEnabled()) {
-            Toast.makeText(mContext, mContext.getResources().getString(R.string.
-                    video_call_not_allowed_if_tty_enabled), Toast.LENGTH_SHORT).show();
+        if (VideoProfile.isVideo(videoState) && isTtyModeEnabled() &&
+                !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+
+            showErrorDialogForRestrictedOutgoingCall(mContext,
+                    R.string.video_call_not_allowed_if_tty_enabled);
             Log.d(this, "Rejecting video calls as tty is enabled");
             return;
         }
@@ -150,6 +151,11 @@
                 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
     }
 
+    private boolean isEmergencyVideoCallingSupported() {
+        return mContext.getResources().getBoolean(
+                R.bool.config_enable_emergency_video_calling);
+    }
+
     private boolean isDefaultOrSystemDialer(String callingPackageName) {
         if (TextUtils.isEmpty(callingPackageName)) {
             return false;
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 a802768..901ba30 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,6 +27,10 @@
         $(call all-java-files-under, src) \
         $(call all-java-files-under, ../src)
 
+LOCAL_SRC_FILES += \
+        src/org/codeaurora/btmultisim/IBluetoothDsdaService.aidl
+
+
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
     $(LOCAL_PATH)/../res
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 964f014..a98f7df 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -141,6 +141,12 @@
         public void onPostDialContinue(String callId, boolean proceed) throws RemoteException { }
 
         @Override
+        public void setLocalCallHold(String callId, boolean lchStatus) throws RemoteException { }
+
+        @Override
+        public void addParticipantWithConference(String callId, String recipients) {}
+
+        @Override
         public IBinder asBinder() {
             return this;
         }
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 2dd4b97..8a7d970 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -113,6 +113,11 @@
         public IInterface queryLocalInterface(String descriptor) {
             return this;
         }
+
+        @Override
+        public void onMergeFailed(ParcelableCall call) {
+
+        }
     }
 
     private IInCallService.Stub mInCallServiceFake = new FakeInCallService();
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();
+}
+