diff options
author | Sandeep Kunta <skunta@codeaurora.org> | 2016-06-14 10:51:14 +0530 |
---|---|---|
committer | Linux Build Service Account <lnxbuild@localhost> | 2016-08-24 08:19:29 -0600 |
commit | 0a5f6ba17f43cd70f4fefa813090f5df958c0604 (patch) | |
tree | dee4c65498fcc1415e5aef88229def15aeeb2d1a | |
parent | 59c834500bd6de0feb2aa93408e85d4c4ae71e50 (diff) | |
download | android_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.xml | 3 | ||||
-rw-r--r-- | src/com/android/server/telecom/CallAudioManager.java | 29 | ||||
-rw-r--r-- | src/com/android/server/telecom/CallsManager.java | 553 | ||||
-rw-r--r-- | src/com/android/server/telecom/ConnectionServiceWrapper.java | 4 | ||||
-rw-r--r-- | src/com/android/server/telecom/DsdaAdapter.java | 61 | ||||
-rw-r--r-- | src/com/android/server/telecom/InCallAdapter.java | 11 | ||||
-rw-r--r-- | src/com/android/server/telecom/InCallController.java | 1 | ||||
-rw-r--r-- | src/com/android/server/telecom/InCallTonePlayer.java | 7 |
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."); |