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