summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSandeep Kunta <skunta@codeaurora.org>2016-06-14 10:51:14 +0530
committerLinux Build Service Account <lnxbuild@localhost>2016-08-24 08:19:29 -0600
commit0a5f6ba17f43cd70f4fefa813090f5df958c0604 (patch)
treedee4c65498fcc1415e5aef88229def15aeeb2d1a
parent59c834500bd6de0feb2aa93408e85d4c4ae71e50 (diff)
downloadandroid_packages_services_Telecomm-0a5f6ba17f43cd70f4fefa813090f5df958c0604.tar.gz
android_packages_services_Telecomm-0a5f6ba17f43cd70f4fefa813090f5df958c0604.tar.bz2
android_packages_services_Telecomm-0a5f6ba17f43cd70f4fefa813090f5df958c0604.zip
MSIM: Add support for DSDA.
Add support for below. 1) Maintain LCH & Active subscriptions and inform changes to lower layers. 2) LCHR and SCH tones 3) Allow calls on other sub, when call is already active in the current sub. CRs-Fixed: 980802 Change-Id: Id6c774496266afbda61c0ab10610221fab3daa47
-rw-r--r--res/values/config.xml3
-rw-r--r--src/com/android/server/telecom/CallAudioManager.java29
-rw-r--r--src/com/android/server/telecom/CallsManager.java553
-rw-r--r--src/com/android/server/telecom/ConnectionServiceWrapper.java4
-rw-r--r--src/com/android/server/telecom/DsdaAdapter.java61
-rw-r--r--src/com/android/server/telecom/InCallAdapter.java11
-rw-r--r--src/com/android/server/telecom/InCallController.java1
-rw-r--r--src/com/android/server/telecom/InCallTonePlayer.java7
8 files changed, 650 insertions, 19 deletions
diff --git a/res/values/config.xml b/res/values/config.xml
index be0f72a6..11fd6199 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -45,4 +45,7 @@
<!-- 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>
</resources>
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 7bc727c4..50156187 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -543,6 +543,22 @@ public class CallAudioManager extends CallsManagerListenerBase {
}
}
+ //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) {
@@ -553,12 +569,15 @@ public class CallAudioManager extends CallsManagerListenerBase {
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;
}
@@ -706,4 +725,4 @@ public class CallAudioManager extends CallsManagerListenerBase {
public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
return mCallStateToCalls;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index b94bf678..d7a919fa 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -25,7 +25,10 @@ import android.net.Uri;
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;
@@ -67,6 +70,7 @@ import com.android.server.telecom.components.ErrorDialogActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -75,6 +79,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import org.codeaurora.internal.IExtTelephony;
/**
* Singleton.
*
@@ -117,6 +122,9 @@ public class CallsManager extends Call.ListenerBase
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};
@@ -198,7 +206,25 @@ public class CallsManager extends Call.ListenerBase
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>();
/**
* Initializes the required Telecom components.
@@ -271,6 +297,7 @@ public class CallsManager extends Call.ListenerBase
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);
@@ -366,7 +393,7 @@ public class CallsManager extends Call.ListenerBase
}
if (result.shouldAllowCall) {
- if (hasMaximumRingingCalls()) {
+ if (hasMaximumRingingCalls(incomingCall.getTargetPhoneAccount().getId())) {
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"ringing calls.");
rejectCallAndLog(incomingCall);
@@ -376,6 +403,7 @@ public class CallsManager extends Call.ListenerBase
rejectCallAndLog(incomingCall);
} else {
addCall(incomingCall);
+ setActiveSubscription(incomingCall.getTargetPhoneAccount().getId());
}
} else {
if (result.shouldReject) {
@@ -972,6 +1000,9 @@ public class CallsManager extends Call.ListenerBase
com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
+ if (!call.isEmergencyCall()) {
+ updateLchStatus(call.getTargetPhoneAccount().getId());
+ }
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1026,19 +1057,20 @@ public class CallsManager extends Call.ListenerBase
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)) {
- if (0 == (foregroundCall.getConnectionCapabilities()
+ if (activeCall != null && activeCall != call &&
+ (activeCall.isActive() ||
+ activeCall.getState() == CallState.DIALING)) {
+ 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();
@@ -1049,8 +1081,8 @@ public class CallsManager extends Call.ListenerBase
}
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.
@@ -1060,7 +1092,7 @@ public class CallsManager extends Call.ListenerBase
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);
@@ -1109,6 +1141,7 @@ public class CallsManager extends Call.ListenerBase
for (CallsManagerListener listener : mListeners) {
listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
}
+ setActiveSubscription(getConversationSub());
call.reject(rejectWithMessage, textMessage);
}
}
@@ -1208,8 +1241,13 @@ public class CallsManager extends Call.ListenerBase
} else {
Log.d(this, "unholding call: (%s)", call);
for (Call c : mCalls) {
+ PhoneAccountHandle ph = call.getTargetPhoneAccount();
+ PhoneAccountHandle ph1 = c.getTargetPhoneAccount();
// Only attempt to hold parent calls and not the individual children.
- if (c != null && c.isAlive() && c != call && c.getParentCall() == null) {
+ // if 'c' is not for same subscription as call, then don't disturb 'c'
+ if (c != null && c.isAlive() && c != call && c.getParentCall() == null
+ && (ph != null && ph1 != null &&
+ isSamePhAccIdOrSipId(ph.getId(), ph1.getId()))) {
c.hold();
}
}
@@ -1304,6 +1342,15 @@ public class CallsManager extends Call.ListenerBase
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("sip") || id2.contains("sip")));
+ Log.d(this, "isSamePhAccIdOrSipId: id1 = " + id1 + " id2 = " + id2 + " ret = " + ret);
+ return ret;
+ }
/** Called by the in-call UI to change the mute state. */
void mute(boolean shouldMute) {
@@ -1336,6 +1383,8 @@ public class CallsManager extends Call.ListenerBase
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);
if (!call.isNewOutgoingCallIntentBroadcastDone()) {
@@ -1374,6 +1423,7 @@ public class CallsManager extends Call.ListenerBase
void markCallAsDialing(Call call) {
setCallState(call, CallState.DIALING, "dialing set explicitly");
maybeMoveToSpeakerPhone(call);
+ setActiveSubscription(call.getTargetPhoneAccount().getId());
}
void markCallAsActive(Call call) {
@@ -1393,7 +1443,35 @@ public class CallsManager extends Call.ListenerBase
*/
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);
+ }
+ if ((subId != null) && (mLchStatus.get(subId) == true)) {
+ 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);
+ }
+ }
}
/**
@@ -1401,6 +1479,11 @@ public class CallsManager extends Call.ListenerBase
*/
void markCallAsRemoved(Call call) {
removeCall(call);
+ if (!hasAnyCalls()) {
+ updateLchStatus(null);
+ setActiveSubscription(null);
+ manageDsdaInCallTones(false);
+ }
if (mLocallyDisconnectingCalls.contains(call)) {
mLocallyDisconnectingCalls.remove(call);
Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
@@ -1451,6 +1534,10 @@ public class CallsManager extends Call.ListenerBase
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;
}
@@ -1508,7 +1595,12 @@ public class CallsManager extends Call.ListenerBase
// 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;
}
}
@@ -1552,7 +1644,7 @@ public class CallsManager extends Call.ListenerBase
@VisibleForTesting
public Call getFirstCallWithState(int... states) {
- return getFirstCallWithState(null, states);
+ return getFirstCallWithState((Call) null, states);
}
/**
@@ -1588,6 +1680,43 @@ public class CallsManager extends Call.ListenerBase
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,
@@ -1772,6 +1901,7 @@ public class CallsManager extends Call.ListenerBase
}
Trace.endSection();
}
+ manageDsdaInCallTones(false);
}
private void updateCanAddCall() {
@@ -1837,18 +1967,44 @@ public class CallsManager extends Call.ListenerBase
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);
}
@@ -1858,6 +2014,10 @@ public class CallsManager extends Call.ListenerBase
}
private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
+ if (TelephonyManager.getDefault().getMultiSimConfiguration()
+ == TelephonyManager.MultiSimVariants.DSDA) {
+ return makeRoomForOutgoingCallForDsda(call, isEmergency);
+ }
if (hasMaximumLiveCalls()) {
// NOTE: If the amount of live calls changes beyond 1, this logic will probably
// have to change.
@@ -1958,6 +2118,44 @@ public class CallsManager extends Call.ListenerBase
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.
*
@@ -2139,4 +2337,331 @@ public class CallsManager extends Call.ListenerBase
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")) {
+ 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);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 9fc9df72..4a3b1947 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -296,6 +296,10 @@ public class ConnectionServiceWrapper extends ServiceBinder {
childCall.setParentCall(null);
} else {
Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
+ if (conferenceCall.getTargetPhoneAccount() == null) {
+ PhoneAccountHandle ph = childCall.getTargetPhoneAccount();
+ conferenceCall.setTargetPhoneAccount(ph);
+ }
childCall.setParentCall(conferenceCall);
}
} else {
diff --git a/src/com/android/server/telecom/DsdaAdapter.java b/src/com/android/server/telecom/DsdaAdapter.java
new file mode 100644
index 00000000..e31a0157
--- /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 31d268e1..e18e3e6d 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -493,4 +493,15 @@ class InCallAdapter extends IInCallAdapter.Stub {
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 7ea98b9d..e14547a6 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -949,6 +949,7 @@ public final class InCallController extends CallsManagerListenerBase {
return false;
}
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 0b0e78bc..d6082d0b 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -70,6 +70,7 @@ public class InCallTonePlayer extends Thread {
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 @@ public class InCallTonePlayer extends Thread {
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.");