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();
+}
+