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