Loading src/com/android/server/telecom/BluetoothPhoneService.java +85 −14 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.os.Message; import android.os.RemoteException; import android.telecom.CallState; import android.telecom.PhoneAccount; import android.telecom.PhoneCapabilities; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; Loading Loading @@ -145,9 +146,16 @@ public final class BluetoothPhoneService extends Service { @Override public boolean listCurrentCalls() throws RemoteException { // only log if it is after we recently updated the headset state or else it can clog // the android log since this can be queried every second. boolean logQuery = mHeadsetUpdatedRecently; mHeadsetUpdatedRecently = false; if (logQuery) { Log.i(TAG, "listcurrentCalls"); } enforceModifyPermission(); return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS); return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0); } @Override Loading Loading @@ -283,7 +291,7 @@ public final class BluetoothPhoneService extends Service { case MSG_LIST_CURRENT_CALLS: try { sendListOfCalls(); sendListOfCalls(request.param == 1); } finally { request.setResult(true); } Loading Loading @@ -371,6 +379,8 @@ public final class BluetoothPhoneService extends Service { // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls). private Map<Call, Integer> mClccIndexMap = new HashMap<>(); private boolean mHeadsetUpdatedRecently = false; public BluetoothPhoneService() { Log.v(TAG, "Constructor"); } Loading Loading @@ -418,6 +428,8 @@ public final class BluetoothPhoneService extends Service { Call ringingCall = callsManager.getRingingCall(); Call heldCall = callsManager.getHeldCall(); Log.v(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall); if (chld == CHLD_TYPE_RELEASEHELD) { if (ringingCall != null) { callsManager.rejectCall(ringingCall, false, null); Loading @@ -437,7 +449,10 @@ public final class BluetoothPhoneService extends Service { return true; } } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { if (ringingCall != null) { if (activeCall != null && activeCall.can(PhoneCapabilities.SWAP_CONFERENCE)) { activeCall.swapConference(); return true; } else if (ringingCall != null) { callsManager.answerCall(ringingCall, 0); return true; } else if (heldCall != null) { Loading @@ -445,12 +460,16 @@ public final class BluetoothPhoneService extends Service { // currently-held call. callsManager.unholdCall(heldCall); return true; } else if (activeCall != null) { } else if (activeCall != null && activeCall.can(PhoneCapabilities.HOLD)) { callsManager.holdCall(activeCall); return true; } } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { if (activeCall != null) { if (activeCall != null && activeCall.can(PhoneCapabilities.MERGE_CONFERENCE)) { activeCall.mergeConference(); return true; } else { List<Call> conferenceable = activeCall.getConferenceableCalls(); if (!conferenceable.isEmpty()) { callsManager.conference(activeCall, conferenceable.get(0)); Loading @@ -458,6 +477,7 @@ public final class BluetoothPhoneService extends Service { } } } } return false; } Loading Loading @@ -489,12 +509,12 @@ public final class BluetoothPhoneService extends Service { return null; } private void sendListOfCalls() { private void sendListOfCalls(boolean shouldLog) { Collection<Call> mCalls = getCallsManager().getCalls(); for (Call call : mCalls) { // We don't send the parent conference call to the bluetooth device. if (!call.isConference()) { sendClccForCall(call); sendClccForCall(call, shouldLog); } } sendClccEndMarker(); Loading @@ -503,17 +523,50 @@ public final class BluetoothPhoneService extends Service { /** * Sends a single clcc (C* List Current Calls) event for the specified call. */ private void sendClccForCall(Call call) { private void sendClccForCall(Call call, boolean shouldLog) { boolean isForeground = getCallsManager().getForegroundCall() == call; int state = convertCallState(call.getState(), isForeground); boolean isPartOfConference = false; if (state == CALL_STATE_IDLE) { return; } Call conferenceCall = call.getParentCall(); if (conferenceCall != null) { isPartOfConference = true; // Run some alternative states for Conference-level merge/swap support. // Basically, if call supports swapping or merging at the conference-level, then we need // to expose the calls as having distinct states (ACTIVE vs HOLD) or the functionality // won't show up on the bluetooth device. // Before doing any special logic, ensure that we are dealing with an ACTIVE call and // that the conference itself has a notion of the current "active" child call. Call activeChild = conferenceCall.getConferenceLevelActiveCall(); if (state == CALL_STATE_ACTIVE && activeChild != null) { // Reevaluate state if we can MERGE or if we can SWAP without previously having // MERGED. boolean shouldReevaluateState = conferenceCall.can(PhoneCapabilities.MERGE_CONFERENCE) || (conferenceCall.can(PhoneCapabilities.SWAP_CONFERENCE) && !conferenceCall.wasConferencePreviouslyMerged()); if (shouldReevaluateState) { isPartOfConference = false; if (call == activeChild) { state = CALL_STATE_ACTIVE; } else { // At this point we know there is an "active" child and we know that it is // not this call, so set it to HELD instead. state = CALL_STATE_HELD; } } } } int index = getIndexForCall(call); int direction = call.isIncoming() ? 1 : 0; boolean isPartOfConference = call.getParentCall() != null; final Uri addressUri; if (call.getGatewayInfo() != null) { addressUri = call.getGatewayInfo().getOriginalAddress(); Loading @@ -523,8 +576,11 @@ public final class BluetoothPhoneService extends Service { String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); if (shouldLog) { Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d", index, direction, state, isPartOfConference, Log.piiHandle(address), addressType); index, direction, state, isPartOfConference, Log.piiHandle(address), addressType); } mBluetoothHeadset.clccResponse( index, direction, state, 0, isPartOfConference, address, addressType); } Loading Loading @@ -576,6 +632,19 @@ public final class BluetoothPhoneService extends Service { int numActiveCalls = activeCall == null ? 0 : 1; int numHeldCalls = heldCall == null ? 0 : 1; // 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. if (activeCall != null && activeCall.isConference()) { if (activeCall.can(PhoneCapabilities.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(PhoneCapabilities.MERGE_CONFERENCE)) { numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. } } if (mBluetoothHeadset != null && (numActiveCalls != mNumActiveCalls || numHeldCalls != mNumHeldCalls || Loading Loading @@ -623,6 +692,8 @@ public final class BluetoothPhoneService extends Service { mBluetoothCallState, mRingingAddress, mRingingAddressType); mHeadsetUpdatedRecently = true; } } Loading src/com/android/server/telecom/Call.java +34 −0 Original line number Diff line number Diff line Loading @@ -278,6 +278,14 @@ final class Call implements CreateConnectionResponse { private final ConnectionServiceRepository mRepository; private final Context mContext; private boolean mWasConferencePreviouslyMerged = false; // For conferences which support merge/swap at their level, we retain a notion of an active call. // This is used for BluetoothPhoneService. In order to support hold/merge, it must have the notion // of the current "active" call within the conference call. This maintains the "active" call and // switches every time the user hits "swap". private Call mConferenceLevelActiveCall = null; /** * Persists the specified parameters and initializes the new instance. * Loading Loading @@ -545,6 +553,14 @@ final class Call implements CreateConnectionResponse { return mChildCalls; } boolean wasConferencePreviouslyMerged() { return mWasConferencePreviouslyMerged; } Call getConferenceLevelActiveCall() { return mConferenceLevelActiveCall; } ConnectionServiceWrapper getConnectionService() { return mConnectionService; } Loading Loading @@ -866,6 +882,7 @@ final class Call implements CreateConnectionResponse { Log.w(this, "merging conference calls without a connection service."); } else if (can(PhoneCapabilities.MERGE_CONFERENCE)) { mConnectionService.mergeConference(this); mWasConferencePreviouslyMerged = true; } } Loading @@ -874,6 +891,20 @@ final class Call implements CreateConnectionResponse { Log.w(this, "swapping conference calls without a connection service."); } else if (can(PhoneCapabilities.SWAP_CONFERENCE)) { mConnectionService.swapConference(this); switch (mChildCalls.size()) { case 1: mConferenceLevelActiveCall = mChildCalls.get(0); break; case 2: // swap mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ? mChildCalls.get(1) : mChildCalls.get(0); break; default: // For anything else 0, or 3+, set it to null since it is impossible to tell. mConferenceLevelActiveCall = null; break; } } } Loading Loading @@ -917,6 +948,9 @@ final class Call implements CreateConnectionResponse { private void addChildCall(Call call) { if (!mChildCalls.contains(call)) { // Set the pseudo-active call to the latest child added to the conference. // See definition of mConferenceLevelActiveCall for more detail. mConferenceLevelActiveCall = call; mChildCalls.add(call); for (Listener l : mListeners) { Loading Loading
src/com/android/server/telecom/BluetoothPhoneService.java +85 −14 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.os.Message; import android.os.RemoteException; import android.telecom.CallState; import android.telecom.PhoneAccount; import android.telecom.PhoneCapabilities; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; Loading Loading @@ -145,9 +146,16 @@ public final class BluetoothPhoneService extends Service { @Override public boolean listCurrentCalls() throws RemoteException { // only log if it is after we recently updated the headset state or else it can clog // the android log since this can be queried every second. boolean logQuery = mHeadsetUpdatedRecently; mHeadsetUpdatedRecently = false; if (logQuery) { Log.i(TAG, "listcurrentCalls"); } enforceModifyPermission(); return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS); return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0); } @Override Loading Loading @@ -283,7 +291,7 @@ public final class BluetoothPhoneService extends Service { case MSG_LIST_CURRENT_CALLS: try { sendListOfCalls(); sendListOfCalls(request.param == 1); } finally { request.setResult(true); } Loading Loading @@ -371,6 +379,8 @@ public final class BluetoothPhoneService extends Service { // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls). private Map<Call, Integer> mClccIndexMap = new HashMap<>(); private boolean mHeadsetUpdatedRecently = false; public BluetoothPhoneService() { Log.v(TAG, "Constructor"); } Loading Loading @@ -418,6 +428,8 @@ public final class BluetoothPhoneService extends Service { Call ringingCall = callsManager.getRingingCall(); Call heldCall = callsManager.getHeldCall(); Log.v(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall); if (chld == CHLD_TYPE_RELEASEHELD) { if (ringingCall != null) { callsManager.rejectCall(ringingCall, false, null); Loading @@ -437,7 +449,10 @@ public final class BluetoothPhoneService extends Service { return true; } } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) { if (ringingCall != null) { if (activeCall != null && activeCall.can(PhoneCapabilities.SWAP_CONFERENCE)) { activeCall.swapConference(); return true; } else if (ringingCall != null) { callsManager.answerCall(ringingCall, 0); return true; } else if (heldCall != null) { Loading @@ -445,12 +460,16 @@ public final class BluetoothPhoneService extends Service { // currently-held call. callsManager.unholdCall(heldCall); return true; } else if (activeCall != null) { } else if (activeCall != null && activeCall.can(PhoneCapabilities.HOLD)) { callsManager.holdCall(activeCall); return true; } } else if (chld == CHLD_TYPE_ADDHELDTOCONF) { if (activeCall != null) { if (activeCall != null && activeCall.can(PhoneCapabilities.MERGE_CONFERENCE)) { activeCall.mergeConference(); return true; } else { List<Call> conferenceable = activeCall.getConferenceableCalls(); if (!conferenceable.isEmpty()) { callsManager.conference(activeCall, conferenceable.get(0)); Loading @@ -458,6 +477,7 @@ public final class BluetoothPhoneService extends Service { } } } } return false; } Loading Loading @@ -489,12 +509,12 @@ public final class BluetoothPhoneService extends Service { return null; } private void sendListOfCalls() { private void sendListOfCalls(boolean shouldLog) { Collection<Call> mCalls = getCallsManager().getCalls(); for (Call call : mCalls) { // We don't send the parent conference call to the bluetooth device. if (!call.isConference()) { sendClccForCall(call); sendClccForCall(call, shouldLog); } } sendClccEndMarker(); Loading @@ -503,17 +523,50 @@ public final class BluetoothPhoneService extends Service { /** * Sends a single clcc (C* List Current Calls) event for the specified call. */ private void sendClccForCall(Call call) { private void sendClccForCall(Call call, boolean shouldLog) { boolean isForeground = getCallsManager().getForegroundCall() == call; int state = convertCallState(call.getState(), isForeground); boolean isPartOfConference = false; if (state == CALL_STATE_IDLE) { return; } Call conferenceCall = call.getParentCall(); if (conferenceCall != null) { isPartOfConference = true; // Run some alternative states for Conference-level merge/swap support. // Basically, if call supports swapping or merging at the conference-level, then we need // to expose the calls as having distinct states (ACTIVE vs HOLD) or the functionality // won't show up on the bluetooth device. // Before doing any special logic, ensure that we are dealing with an ACTIVE call and // that the conference itself has a notion of the current "active" child call. Call activeChild = conferenceCall.getConferenceLevelActiveCall(); if (state == CALL_STATE_ACTIVE && activeChild != null) { // Reevaluate state if we can MERGE or if we can SWAP without previously having // MERGED. boolean shouldReevaluateState = conferenceCall.can(PhoneCapabilities.MERGE_CONFERENCE) || (conferenceCall.can(PhoneCapabilities.SWAP_CONFERENCE) && !conferenceCall.wasConferencePreviouslyMerged()); if (shouldReevaluateState) { isPartOfConference = false; if (call == activeChild) { state = CALL_STATE_ACTIVE; } else { // At this point we know there is an "active" child and we know that it is // not this call, so set it to HELD instead. state = CALL_STATE_HELD; } } } } int index = getIndexForCall(call); int direction = call.isIncoming() ? 1 : 0; boolean isPartOfConference = call.getParentCall() != null; final Uri addressUri; if (call.getGatewayInfo() != null) { addressUri = call.getGatewayInfo().getOriginalAddress(); Loading @@ -523,8 +576,11 @@ public final class BluetoothPhoneService extends Service { String address = addressUri == null ? null : addressUri.getSchemeSpecificPart(); int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address); if (shouldLog) { Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d", index, direction, state, isPartOfConference, Log.piiHandle(address), addressType); index, direction, state, isPartOfConference, Log.piiHandle(address), addressType); } mBluetoothHeadset.clccResponse( index, direction, state, 0, isPartOfConference, address, addressType); } Loading Loading @@ -576,6 +632,19 @@ public final class BluetoothPhoneService extends Service { int numActiveCalls = activeCall == null ? 0 : 1; int numHeldCalls = heldCall == null ? 0 : 1; // 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. if (activeCall != null && activeCall.isConference()) { if (activeCall.can(PhoneCapabilities.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(PhoneCapabilities.MERGE_CONFERENCE)) { numHeldCalls = 1; // Merge is available, so expose via numHeldCalls. } } if (mBluetoothHeadset != null && (numActiveCalls != mNumActiveCalls || numHeldCalls != mNumHeldCalls || Loading Loading @@ -623,6 +692,8 @@ public final class BluetoothPhoneService extends Service { mBluetoothCallState, mRingingAddress, mRingingAddressType); mHeadsetUpdatedRecently = true; } } Loading
src/com/android/server/telecom/Call.java +34 −0 Original line number Diff line number Diff line Loading @@ -278,6 +278,14 @@ final class Call implements CreateConnectionResponse { private final ConnectionServiceRepository mRepository; private final Context mContext; private boolean mWasConferencePreviouslyMerged = false; // For conferences which support merge/swap at their level, we retain a notion of an active call. // This is used for BluetoothPhoneService. In order to support hold/merge, it must have the notion // of the current "active" call within the conference call. This maintains the "active" call and // switches every time the user hits "swap". private Call mConferenceLevelActiveCall = null; /** * Persists the specified parameters and initializes the new instance. * Loading Loading @@ -545,6 +553,14 @@ final class Call implements CreateConnectionResponse { return mChildCalls; } boolean wasConferencePreviouslyMerged() { return mWasConferencePreviouslyMerged; } Call getConferenceLevelActiveCall() { return mConferenceLevelActiveCall; } ConnectionServiceWrapper getConnectionService() { return mConnectionService; } Loading Loading @@ -866,6 +882,7 @@ final class Call implements CreateConnectionResponse { Log.w(this, "merging conference calls without a connection service."); } else if (can(PhoneCapabilities.MERGE_CONFERENCE)) { mConnectionService.mergeConference(this); mWasConferencePreviouslyMerged = true; } } Loading @@ -874,6 +891,20 @@ final class Call implements CreateConnectionResponse { Log.w(this, "swapping conference calls without a connection service."); } else if (can(PhoneCapabilities.SWAP_CONFERENCE)) { mConnectionService.swapConference(this); switch (mChildCalls.size()) { case 1: mConferenceLevelActiveCall = mChildCalls.get(0); break; case 2: // swap mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ? mChildCalls.get(1) : mChildCalls.get(0); break; default: // For anything else 0, or 3+, set it to null since it is impossible to tell. mConferenceLevelActiveCall = null; break; } } } Loading Loading @@ -917,6 +948,9 @@ final class Call implements CreateConnectionResponse { private void addChildCall(Call call) { if (!mChildCalls.contains(call)) { // Set the pseudo-active call to the latest child added to the conference. // See definition of mConferenceLevelActiveCall for more detail. mConferenceLevelActiveCall = call; mChildCalls.add(call); for (Listener l : mListeners) { Loading