Loading src/com/android/server/telecom/CallsManager.java +215 −54 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.telecom.VideoProfile; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.AsyncEmergencyContactNotifier; Loading @@ -68,6 +69,7 @@ import com.android.server.telecom.callfiltering.IncomingCallFilter; import com.android.server.telecom.components.ErrorDialogActivity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; Loading @@ -79,6 +81,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; /** * Singleton. Loading Loading @@ -122,6 +127,7 @@ 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_SELF_MANAGED_CALLS = 10; private static final int[] OUTGOING_CALL_STATES = {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, Loading @@ -131,6 +137,11 @@ public class CallsManager extends Call.ListenerBase {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE}; private static final int[] ANY_CALL_STATE = {CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED, CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING}; public static final String TELECOM_CALL_ID_PREFIX = "TC@"; // Maps call technologies in PhoneConstants to those in Analytics. Loading Loading @@ -387,7 +398,7 @@ public class CallsManager extends Call.ListenerBase } if (result.shouldAllowCall) { if (hasMaximumRingingCalls()) { if (hasMaximumManagedRingingCalls(incomingCall)) { if (shouldSilenceInsteadOfReject(incomingCall)) { incomingCall.silence(); } else { Loading @@ -395,7 +406,7 @@ public class CallsManager extends Call.ListenerBase "Exceeds maximum number of ringing calls."); rejectCallAndLog(incomingCall); } } else if (hasMaximumDialingCalls()) { } else if (hasMaximumManagedDialingCalls(incomingCall)) { Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " + "dialing calls."); rejectCallAndLog(incomingCall); Loading Loading @@ -749,8 +760,13 @@ public class CallsManager extends Call.ListenerBase setIntentExtrasAndStartTime(call, extras); // TODO: Move this to be a part of addCall() call.addListener(this); if (call.isSelfManaged() && !isIncomingCallPermitted(call, call.getTargetPhoneAccount())) { notifyCreateConnectionFailed(phoneAccountHandle, call); } else { call.startCreateConnection(mPhoneAccountRegistrar); } } void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE); Loading Loading @@ -815,7 +831,11 @@ public class CallsManager extends Call.ListenerBase } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * Kicks off the first steps to creating an outgoing call. * * For managed connections, this is the first step to launching the Incall UI. * For self-managed connections, we don't expect the Incall UI to launch, but this is still a * first step in getting the self-managed ConnectionService to create the connection. * * @param handle Handle to connect the call with. * @param phoneAccountHandle The phone account which contains the component name of the Loading Loading @@ -895,11 +915,17 @@ public class CallsManager extends Call.ListenerBase call.setVideoState(videoState); } List<PhoneAccountHandle> accounts = constructPossiblePhoneAccounts(handle, initiatingUser); PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccount( phoneAccountHandle, initiatingUser); boolean isSelfManaged = targetPhoneAccount != null && targetPhoneAccount.isSelfManaged(); List<PhoneAccountHandle> accounts; if (!isSelfManaged) { accounts = constructPossiblePhoneAccounts(handle, initiatingUser); Log.v(this, "startOutgoingCall found accounts = " + accounts); // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call // as if a phoneAccount was not specified (does the default behavior instead). // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this // call as if a phoneAccount was not specified (does the default behavior instead). // Note: We will not attempt to dial with a requested phoneAccount if it is disabled. if (phoneAccountHandle != null) { if (!accounts.contains(phoneAccountHandle)) { Loading @@ -912,8 +938,8 @@ public class CallsManager extends Call.ListenerBase // handle and verify it can be used. if (accounts.size() > 1) { PhoneAccountHandle defaultPhoneAccountHandle = mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme(), initiatingUser); mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme( handle.getScheme(), initiatingUser); if (defaultPhoneAccountHandle != null && accounts.contains(defaultPhoneAccountHandle)) { phoneAccountHandle = defaultPhoneAccountHandle; Loading @@ -922,18 +948,20 @@ public class CallsManager extends Call.ListenerBase // Use the only PhoneAccount that is available phoneAccountHandle = accounts.get(0); } } } else { accounts = Collections.EMPTY_LIST; } call.setTargetPhoneAccount(phoneAccountHandle); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged; // Do not support any more live calls. Our options are to move a call to hold, disconnect // a call, or cancel this call altogether. If a call is being reused, then it has already // passed the makeRoomForOutgoingCall check once and will fail the second time due to the // call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall && if (!isSelfManaged && !isPotentialInCallMMICode && (!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) { // just cancel at this point. Log.i(this, "No remaining room for outgoing call: %s", call); Loading @@ -946,7 +974,7 @@ public class CallsManager extends Call.ListenerBase } boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 && !call.isEmergencyCall(); !call.isEmergencyCall() && !isSelfManaged; if (needsAccountSelection) { // This is the state where the user is expected to select an account Loading Loading @@ -1033,7 +1061,13 @@ public class CallsManager extends Call.ListenerBase if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) { // 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. if (call.isSelfManaged() && !isOutgoingCallPermitted(call, call.getTargetPhoneAccount())) { notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call); } else { call.startCreateConnection(mPhoneAccountRegistrar); } } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts( requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false, call.getInitiatingUser()).isEmpty()) { Loading Loading @@ -1939,42 +1973,92 @@ public class CallsManager extends Call.ListenerBase return false; } private int getNumCallsWithState(int... states) { int count = 0; for (int state : states) { for (Call call : mCalls) { if (call.getParentCall() == null && call.getState() == state && !call.isExternalCall()) { @VisibleForTesting public int getNumCallsWithState(final boolean isSelfManaged, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { count++; Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet()); Stream<Call> callsStream = mCalls.stream() .filter(call -> desiredStates.contains(call.getState()) && call.getParentCall() == null && !call.isExternalCall() && call.isSelfManaged() == isSelfManaged); // If a call to exclude was specifeid, filter it out. if (excludeCall != null) { callsStream = callsStream.filter(call -> call != excludeCall); } // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } private boolean hasMaximumManagedLiveCalls(Call exceptCall) { return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, LIVE_CALL_STATES); } return count; private boolean hasMaximumSelfManagedCalls(Call exceptCall, PhoneAccountHandle phoneAccountHandle) { return MAXIMUM_SELF_MANAGED_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall, phoneAccountHandle, ANY_CALL_STATE); } private boolean hasMaximumLiveCalls() { return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES); private boolean hasMaximumManagedHoldingCalls(Call exceptCall) { return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.ON_HOLD); } private boolean hasMaximumHoldingCalls() { return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD); private boolean hasMaximumManagedRingingCalls(Call exceptCall) { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.RINGING); } private boolean hasMaximumRingingCalls() { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING); private boolean hasMaximumSelfManagedRingingCalls(Call exceptCall, PhoneAccountHandle phoneAccountHandle) { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall, phoneAccountHandle, CallState.RINGING); } private boolean hasMaximumOutgoingCalls() { return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES); private boolean hasMaximumManagedOutgoingCalls(Call exceptCall) { return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, OUTGOING_CALL_STATES); } private boolean hasMaximumDialingCalls() { return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING, CallState.PULLING); private boolean hasMaximumManagedDialingCalls(Call exceptCall) { return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.DIALING, CallState.PULLING); } /** * Given a {@link PhoneAccountHandle} determines if there are calls owned by any other * {@link PhoneAccountHandle}. * @param phoneAccountHandle The {@link PhoneAccountHandle} to check. * @return {@code true} if there are other calls, {@code false} otherwise. */ public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) { return mCalls.stream().filter(call -> !phoneAccountHandle.equals(call.getTargetPhoneAccount()) && call.getParentCall() == null && !call.isExternalCall()).count() > 0; } /** * Given a {@link PhoneAccountHandle} determines if there are and managed calls. * @return {@code true} if there are managed calls, {@code false} otherwise. */ public boolean hasManagedCalls() { return mCalls.stream().filter(call -> !call.isSelfManaged() && !call.isExternalCall()).count() > 0; } private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { if (hasMaximumLiveCalls()) { if (hasMaximumManagedLiveCalls(call)) { // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Loading @@ -1988,7 +2072,7 @@ public class CallsManager extends Call.ListenerBase return true; } if (hasMaximumOutgoingCalls()) { if (hasMaximumManagedOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the Loading @@ -2010,7 +2094,7 @@ public class CallsManager extends Call.ListenerBase return false; } if (hasMaximumHoldingCalls()) { if (hasMaximumManagedHoldingCalls(call)) { // There is no more room for any more calls, unless it's an emergency. if (isEmergency) { // Kill the current active call, this is easier then trying to disconnect a Loading Loading @@ -2221,6 +2305,61 @@ public class CallsManager extends Call.ListenerBase new MissedCallNotifier.CallInfoFactory()); } public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) { return isIncomingCallPermitted(null /* excludeCall */, phoneAccountHandle); } public boolean isIncomingCallPermitted(Call excludeCall, PhoneAccountHandle phoneAccountHandle) { if (phoneAccountHandle == null) { return false; } PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle); if (phoneAccount == null) { return false; } if (!phoneAccount.isSelfManaged()) { return !hasMaximumManagedRingingCalls(excludeCall) && !hasMaximumManagedHoldingCalls(excludeCall); } else { return !hasEmergencyCall() && !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) && !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) && !hasManagedCalls(); } } public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) { return isOutgoingCallPermitted(null /* excludeCall */, phoneAccountHandle); } public boolean isOutgoingCallPermitted(Call excludeCall, PhoneAccountHandle phoneAccountHandle) { if (phoneAccountHandle == null) { return false; } PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle); if (phoneAccount == null) { return false; } if (!phoneAccount.isSelfManaged()) { return !hasMaximumManagedOutgoingCalls(excludeCall) && !hasMaximumManagedDialingCalls(excludeCall) && !hasMaximumManagedLiveCalls(excludeCall) && !hasMaximumManagedHoldingCalls(excludeCall); } else { // Only permit outgoing calls if there is no ongoing emergency calls and all other calls // are associated with the current PhoneAccountHandle. return !hasEmergencyCall() && !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) && !hasCallsForOtherPhoneAccount(phoneAccountHandle); } } /** * Dumps the state of the {@link CallsManager}. * Loading Loading @@ -2304,4 +2443,26 @@ public class CallsManager extends Call.ListenerBase call.setIntentExtras(extras); } /** * Notifies the {@link android.telecom.ConnectionService} associated with a * {@link PhoneAccountHandle} that the attempt to create a new connection has failed. * * @param phoneAccountHandle The {@link PhoneAccountHandle}. * @param call The {@link Call} which could not be added. */ private void notifyCreateConnectionFailed(PhoneAccountHandle phoneAccountHandle, Call call) { if (phoneAccountHandle == null) { return; } ConnectionServiceWrapper service = mConnectionServiceRepository.getService( phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle()); if (service == null) { Log.i(this, "Found no connection service."); return; } else { call.setConnectionService(service); service.createConnectionFailed(call); } } } src/com/android/server/telecom/ConnectionServiceWrapper.java +53 −2 Original line number Diff line number Diff line Loading @@ -862,6 +862,13 @@ public class ConnectionServiceWrapper extends ServiceBinder { Log.addEvent(call, LogUtils.Events.START_CONNECTION, Log.piiHandle(call.getHandle())); try { // For self-managed incoming calls, if there is another ongoing call Telecom is // responsible for showing a UI to ask the user if they'd like to answer this // new incoming call. boolean shouldShowIncomingCallUI = call.isSelfManaged() && !mCallsManager.hasCallsForOtherPhoneAccount( call.getTargetPhoneAccount()); mServiceInterface.createConnection( call.getConnectionManagerPhoneAccount(), callId, Loading @@ -871,11 +878,11 @@ public class ConnectionServiceWrapper extends ServiceBinder { extras, call.getVideoState(), callId, false), // TODO(3pcalls): pass in true/false based on whether ux // should show. shouldShowIncomingCallUI), call.shouldAttachToExistingConnection(), call.isUnknown(), Log.getExternalSession()); } catch (RemoteException e) { Log.e(this, e, "Failure to createConnection -- %s", getComponentName()); mPendingResponses.remove(callId).handleCreateConnectionFailure( Loading @@ -893,6 +900,50 @@ public class ConnectionServiceWrapper extends ServiceBinder { mBinder.bind(callback, call); } /** * Notifies the {@link ConnectionService} associated with a {@link Call} that the request to * create a connection has been denied or failed. * @param call The call. */ void createConnectionFailed(final Call call) { Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName()); BindCallback callback = new BindCallback() { @Override public void onSuccess() { final String callId = mCallIdMapper.getCallId(call); // If still bound, tell the connection service create connection has failed. if (callId != null && isServiceValid("createConnectionFailed")) { Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_FAILED, Log.piiHandle(call.getHandle())); try { logOutgoing("createConnectionFailed %s", callId); mServiceInterface.createConnectionFailed(callId, new ConnectionRequest( call.getTargetPhoneAccount(), call.getHandle(), call.getIntentExtras(), call.getVideoState(), callId, false), call.isIncoming(), Log.getExternalSession()); call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED)); call.disconnect(); } catch (RemoteException e) { } } } @Override public void onFailure() { // Binding failed. Oh no. Log.w(this, "onFailure - could not bind to CS for call %s", call.getId()); } }; mBinder.bind(callback, call); } /** @see IConnectionService#abort(String, Session.Info) */ void abort(Call call) { // Clear out any pending outgoing call data Loading src/com/android/server/telecom/CreateConnectionProcessor.java +4 −2 Original line number Diff line number Diff line Loading @@ -132,8 +132,10 @@ public class CreateConnectionProcessor implements CreateConnectionResponse { mAttemptRecords.add(new CallAttemptRecord( mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); } if (!mCall.isSelfManaged()) { adjustAttemptsForConnectionManager(); adjustAttemptsForEmergency(mCall.getTargetPhoneAccount()); } mAttemptRecordIterator = mAttemptRecords.iterator(); attemptNextPhoneAccount(); } Loading src/com/android/server/telecom/InCallController.java +32 −1 Original line number Diff line number Diff line Loading @@ -638,6 +638,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallAdded(Call call) { if (call.isSelfManaged()) { Log.i(this, "onCallAdded: skipped self-managed %s", call); return; } if (!isBoundToServices()) { bindToServices(call); } else { Loading Loading @@ -672,6 +677,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallRemoved(Call call) { if (call.isSelfManaged()) { Log.i(this, "onCallRemoved: skipped self-managed %s", call); return; } Log.i(this, "onCallRemoved: %s", call); if (mCallsManager.getCalls().isEmpty()) { /** Let's add a 2 second delay before we send unbind to the services to hopefully Loading @@ -695,6 +705,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onExternalCallChanged(Call call, boolean isExternalCall) { if (call.isSelfManaged()) { Log.i(this, "onExternalCallChanged: skipped self-managed %s", call); return; } Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall); List<ComponentName> componentsUpdated = new ArrayList<>(); Loading Loading @@ -756,6 +771,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallStateChanged(Call call, int oldState, int newState) { if (call.isSelfManaged()) { Log.i(this, "onExternalCallChanged: skipped self-managed %s", call); return; } updateCall(call); } Loading @@ -764,6 +784,11 @@ public final class InCallController extends CallsManagerListenerBase { Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService) { if (call.isSelfManaged()) { Log.i(this, "onConnectionServiceChanged: skipped self-managed %s", call); return; } updateCall(call); } Loading Loading @@ -809,6 +834,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onIsConferencedChanged(Call call) { if (call.isSelfManaged()) { Log.i(this, "onIsConferencedChanged: skipped self-managed %s", call); return; } Log.d(this, "onIsConferencedChanged %s", call); updateCall(call); } Loading Loading @@ -1103,7 +1133,8 @@ public final class InCallController extends CallsManagerListenerBase { int numCallsSent = 0; for (Call call : calls) { try { if (call.isExternalCall() && !info.isExternalCallsSupported()) { if (call.isSelfManaged() || (call.isExternalCall() && !info.isExternalCallsSupported())) { continue; } Loading src/com/android/server/telecom/LogUtils.java +1 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ public class LogUtils { public static final String START_CALL_WAITING_TONE = "START_CALL_WAITING_TONE"; public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE"; public static final String START_CONNECTION = "START_CONNECTION"; public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED"; public static final String BIND_CS = "BIND_CS"; public static final String CS_BOUND = "CS_BOUND"; public static final String CONFERENCE_WITH = "CONF_WITH"; Loading Loading
src/com/android/server/telecom/CallsManager.java +215 −54 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.telecom.VideoProfile; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.AsyncEmergencyContactNotifier; Loading @@ -68,6 +69,7 @@ import com.android.server.telecom.callfiltering.IncomingCallFilter; import com.android.server.telecom.components.ErrorDialogActivity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; Loading @@ -79,6 +81,9 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; /** * Singleton. Loading Loading @@ -122,6 +127,7 @@ 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_SELF_MANAGED_CALLS = 10; private static final int[] OUTGOING_CALL_STATES = {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, Loading @@ -131,6 +137,11 @@ public class CallsManager extends Call.ListenerBase {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE}; private static final int[] ANY_CALL_STATE = {CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED, CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING}; public static final String TELECOM_CALL_ID_PREFIX = "TC@"; // Maps call technologies in PhoneConstants to those in Analytics. Loading Loading @@ -387,7 +398,7 @@ public class CallsManager extends Call.ListenerBase } if (result.shouldAllowCall) { if (hasMaximumRingingCalls()) { if (hasMaximumManagedRingingCalls(incomingCall)) { if (shouldSilenceInsteadOfReject(incomingCall)) { incomingCall.silence(); } else { Loading @@ -395,7 +406,7 @@ public class CallsManager extends Call.ListenerBase "Exceeds maximum number of ringing calls."); rejectCallAndLog(incomingCall); } } else if (hasMaximumDialingCalls()) { } else if (hasMaximumManagedDialingCalls(incomingCall)) { Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " + "dialing calls."); rejectCallAndLog(incomingCall); Loading Loading @@ -749,8 +760,13 @@ public class CallsManager extends Call.ListenerBase setIntentExtrasAndStartTime(call, extras); // TODO: Move this to be a part of addCall() call.addListener(this); if (call.isSelfManaged() && !isIncomingCallPermitted(call, call.getTargetPhoneAccount())) { notifyCreateConnectionFailed(phoneAccountHandle, call); } else { call.startCreateConnection(mPhoneAccountRegistrar); } } void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE); Loading Loading @@ -815,7 +831,11 @@ public class CallsManager extends Call.ListenerBase } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * Kicks off the first steps to creating an outgoing call. * * For managed connections, this is the first step to launching the Incall UI. * For self-managed connections, we don't expect the Incall UI to launch, but this is still a * first step in getting the self-managed ConnectionService to create the connection. * * @param handle Handle to connect the call with. * @param phoneAccountHandle The phone account which contains the component name of the Loading Loading @@ -895,11 +915,17 @@ public class CallsManager extends Call.ListenerBase call.setVideoState(videoState); } List<PhoneAccountHandle> accounts = constructPossiblePhoneAccounts(handle, initiatingUser); PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccount( phoneAccountHandle, initiatingUser); boolean isSelfManaged = targetPhoneAccount != null && targetPhoneAccount.isSelfManaged(); List<PhoneAccountHandle> accounts; if (!isSelfManaged) { accounts = constructPossiblePhoneAccounts(handle, initiatingUser); Log.v(this, "startOutgoingCall found accounts = " + accounts); // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call // as if a phoneAccount was not specified (does the default behavior instead). // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this // call as if a phoneAccount was not specified (does the default behavior instead). // Note: We will not attempt to dial with a requested phoneAccount if it is disabled. if (phoneAccountHandle != null) { if (!accounts.contains(phoneAccountHandle)) { Loading @@ -912,8 +938,8 @@ public class CallsManager extends Call.ListenerBase // handle and verify it can be used. if (accounts.size() > 1) { PhoneAccountHandle defaultPhoneAccountHandle = mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme(), initiatingUser); mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme( handle.getScheme(), initiatingUser); if (defaultPhoneAccountHandle != null && accounts.contains(defaultPhoneAccountHandle)) { phoneAccountHandle = defaultPhoneAccountHandle; Loading @@ -922,18 +948,20 @@ public class CallsManager extends Call.ListenerBase // Use the only PhoneAccount that is available phoneAccountHandle = accounts.get(0); } } } else { accounts = Collections.EMPTY_LIST; } call.setTargetPhoneAccount(phoneAccountHandle); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged; // Do not support any more live calls. Our options are to move a call to hold, disconnect // a call, or cancel this call altogether. If a call is being reused, then it has already // passed the makeRoomForOutgoingCall check once and will fail the second time due to the // call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall && if (!isSelfManaged && !isPotentialInCallMMICode && (!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) { // just cancel at this point. Log.i(this, "No remaining room for outgoing call: %s", call); Loading @@ -946,7 +974,7 @@ public class CallsManager extends Call.ListenerBase } boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 && !call.isEmergencyCall(); !call.isEmergencyCall() && !isSelfManaged; if (needsAccountSelection) { // This is the state where the user is expected to select an account Loading Loading @@ -1033,7 +1061,13 @@ public class CallsManager extends Call.ListenerBase if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) { // 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. if (call.isSelfManaged() && !isOutgoingCallPermitted(call, call.getTargetPhoneAccount())) { notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call); } else { call.startCreateConnection(mPhoneAccountRegistrar); } } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts( requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false, call.getInitiatingUser()).isEmpty()) { Loading Loading @@ -1939,42 +1973,92 @@ public class CallsManager extends Call.ListenerBase return false; } private int getNumCallsWithState(int... states) { int count = 0; for (int state : states) { for (Call call : mCalls) { if (call.getParentCall() == null && call.getState() == state && !call.isExternalCall()) { @VisibleForTesting public int getNumCallsWithState(final boolean isSelfManaged, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { count++; Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet()); Stream<Call> callsStream = mCalls.stream() .filter(call -> desiredStates.contains(call.getState()) && call.getParentCall() == null && !call.isExternalCall() && call.isSelfManaged() == isSelfManaged); // If a call to exclude was specifeid, filter it out. if (excludeCall != null) { callsStream = callsStream.filter(call -> call != excludeCall); } // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } private boolean hasMaximumManagedLiveCalls(Call exceptCall) { return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, LIVE_CALL_STATES); } return count; private boolean hasMaximumSelfManagedCalls(Call exceptCall, PhoneAccountHandle phoneAccountHandle) { return MAXIMUM_SELF_MANAGED_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall, phoneAccountHandle, ANY_CALL_STATE); } private boolean hasMaximumLiveCalls() { return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES); private boolean hasMaximumManagedHoldingCalls(Call exceptCall) { return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.ON_HOLD); } private boolean hasMaximumHoldingCalls() { return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD); private boolean hasMaximumManagedRingingCalls(Call exceptCall) { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.RINGING); } private boolean hasMaximumRingingCalls() { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING); private boolean hasMaximumSelfManagedRingingCalls(Call exceptCall, PhoneAccountHandle phoneAccountHandle) { return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall, phoneAccountHandle, CallState.RINGING); } private boolean hasMaximumOutgoingCalls() { return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES); private boolean hasMaximumManagedOutgoingCalls(Call exceptCall) { return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, OUTGOING_CALL_STATES); } private boolean hasMaximumDialingCalls() { return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING, CallState.PULLING); private boolean hasMaximumManagedDialingCalls(Call exceptCall) { return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall, null /* phoneAccountHandle */, CallState.DIALING, CallState.PULLING); } /** * Given a {@link PhoneAccountHandle} determines if there are calls owned by any other * {@link PhoneAccountHandle}. * @param phoneAccountHandle The {@link PhoneAccountHandle} to check. * @return {@code true} if there are other calls, {@code false} otherwise. */ public boolean hasCallsForOtherPhoneAccount(PhoneAccountHandle phoneAccountHandle) { return mCalls.stream().filter(call -> !phoneAccountHandle.equals(call.getTargetPhoneAccount()) && call.getParentCall() == null && !call.isExternalCall()).count() > 0; } /** * Given a {@link PhoneAccountHandle} determines if there are and managed calls. * @return {@code true} if there are managed calls, {@code false} otherwise. */ public boolean hasManagedCalls() { return mCalls.stream().filter(call -> !call.isSelfManaged() && !call.isExternalCall()).count() > 0; } private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { if (hasMaximumLiveCalls()) { if (hasMaximumManagedLiveCalls(call)) { // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Loading @@ -1988,7 +2072,7 @@ public class CallsManager extends Call.ListenerBase return true; } if (hasMaximumOutgoingCalls()) { if (hasMaximumManagedOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the Loading @@ -2010,7 +2094,7 @@ public class CallsManager extends Call.ListenerBase return false; } if (hasMaximumHoldingCalls()) { if (hasMaximumManagedHoldingCalls(call)) { // There is no more room for any more calls, unless it's an emergency. if (isEmergency) { // Kill the current active call, this is easier then trying to disconnect a Loading Loading @@ -2221,6 +2305,61 @@ public class CallsManager extends Call.ListenerBase new MissedCallNotifier.CallInfoFactory()); } public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) { return isIncomingCallPermitted(null /* excludeCall */, phoneAccountHandle); } public boolean isIncomingCallPermitted(Call excludeCall, PhoneAccountHandle phoneAccountHandle) { if (phoneAccountHandle == null) { return false; } PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle); if (phoneAccount == null) { return false; } if (!phoneAccount.isSelfManaged()) { return !hasMaximumManagedRingingCalls(excludeCall) && !hasMaximumManagedHoldingCalls(excludeCall); } else { return !hasEmergencyCall() && !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) && !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) && !hasManagedCalls(); } } public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) { return isOutgoingCallPermitted(null /* excludeCall */, phoneAccountHandle); } public boolean isOutgoingCallPermitted(Call excludeCall, PhoneAccountHandle phoneAccountHandle) { if (phoneAccountHandle == null) { return false; } PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle); if (phoneAccount == null) { return false; } if (!phoneAccount.isSelfManaged()) { return !hasMaximumManagedOutgoingCalls(excludeCall) && !hasMaximumManagedDialingCalls(excludeCall) && !hasMaximumManagedLiveCalls(excludeCall) && !hasMaximumManagedHoldingCalls(excludeCall); } else { // Only permit outgoing calls if there is no ongoing emergency calls and all other calls // are associated with the current PhoneAccountHandle. return !hasEmergencyCall() && !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) && !hasCallsForOtherPhoneAccount(phoneAccountHandle); } } /** * Dumps the state of the {@link CallsManager}. * Loading Loading @@ -2304,4 +2443,26 @@ public class CallsManager extends Call.ListenerBase call.setIntentExtras(extras); } /** * Notifies the {@link android.telecom.ConnectionService} associated with a * {@link PhoneAccountHandle} that the attempt to create a new connection has failed. * * @param phoneAccountHandle The {@link PhoneAccountHandle}. * @param call The {@link Call} which could not be added. */ private void notifyCreateConnectionFailed(PhoneAccountHandle phoneAccountHandle, Call call) { if (phoneAccountHandle == null) { return; } ConnectionServiceWrapper service = mConnectionServiceRepository.getService( phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle()); if (service == null) { Log.i(this, "Found no connection service."); return; } else { call.setConnectionService(service); service.createConnectionFailed(call); } } }
src/com/android/server/telecom/ConnectionServiceWrapper.java +53 −2 Original line number Diff line number Diff line Loading @@ -862,6 +862,13 @@ public class ConnectionServiceWrapper extends ServiceBinder { Log.addEvent(call, LogUtils.Events.START_CONNECTION, Log.piiHandle(call.getHandle())); try { // For self-managed incoming calls, if there is another ongoing call Telecom is // responsible for showing a UI to ask the user if they'd like to answer this // new incoming call. boolean shouldShowIncomingCallUI = call.isSelfManaged() && !mCallsManager.hasCallsForOtherPhoneAccount( call.getTargetPhoneAccount()); mServiceInterface.createConnection( call.getConnectionManagerPhoneAccount(), callId, Loading @@ -871,11 +878,11 @@ public class ConnectionServiceWrapper extends ServiceBinder { extras, call.getVideoState(), callId, false), // TODO(3pcalls): pass in true/false based on whether ux // should show. shouldShowIncomingCallUI), call.shouldAttachToExistingConnection(), call.isUnknown(), Log.getExternalSession()); } catch (RemoteException e) { Log.e(this, e, "Failure to createConnection -- %s", getComponentName()); mPendingResponses.remove(callId).handleCreateConnectionFailure( Loading @@ -893,6 +900,50 @@ public class ConnectionServiceWrapper extends ServiceBinder { mBinder.bind(callback, call); } /** * Notifies the {@link ConnectionService} associated with a {@link Call} that the request to * create a connection has been denied or failed. * @param call The call. */ void createConnectionFailed(final Call call) { Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName()); BindCallback callback = new BindCallback() { @Override public void onSuccess() { final String callId = mCallIdMapper.getCallId(call); // If still bound, tell the connection service create connection has failed. if (callId != null && isServiceValid("createConnectionFailed")) { Log.addEvent(call, LogUtils.Events.CREATE_CONNECTION_FAILED, Log.piiHandle(call.getHandle())); try { logOutgoing("createConnectionFailed %s", callId); mServiceInterface.createConnectionFailed(callId, new ConnectionRequest( call.getTargetPhoneAccount(), call.getHandle(), call.getIntentExtras(), call.getVideoState(), callId, false), call.isIncoming(), Log.getExternalSession()); call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED)); call.disconnect(); } catch (RemoteException e) { } } } @Override public void onFailure() { // Binding failed. Oh no. Log.w(this, "onFailure - could not bind to CS for call %s", call.getId()); } }; mBinder.bind(callback, call); } /** @see IConnectionService#abort(String, Session.Info) */ void abort(Call call) { // Clear out any pending outgoing call data Loading
src/com/android/server/telecom/CreateConnectionProcessor.java +4 −2 Original line number Diff line number Diff line Loading @@ -132,8 +132,10 @@ public class CreateConnectionProcessor implements CreateConnectionResponse { mAttemptRecords.add(new CallAttemptRecord( mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); } if (!mCall.isSelfManaged()) { adjustAttemptsForConnectionManager(); adjustAttemptsForEmergency(mCall.getTargetPhoneAccount()); } mAttemptRecordIterator = mAttemptRecords.iterator(); attemptNextPhoneAccount(); } Loading
src/com/android/server/telecom/InCallController.java +32 −1 Original line number Diff line number Diff line Loading @@ -638,6 +638,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallAdded(Call call) { if (call.isSelfManaged()) { Log.i(this, "onCallAdded: skipped self-managed %s", call); return; } if (!isBoundToServices()) { bindToServices(call); } else { Loading Loading @@ -672,6 +677,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallRemoved(Call call) { if (call.isSelfManaged()) { Log.i(this, "onCallRemoved: skipped self-managed %s", call); return; } Log.i(this, "onCallRemoved: %s", call); if (mCallsManager.getCalls().isEmpty()) { /** Let's add a 2 second delay before we send unbind to the services to hopefully Loading @@ -695,6 +705,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onExternalCallChanged(Call call, boolean isExternalCall) { if (call.isSelfManaged()) { Log.i(this, "onExternalCallChanged: skipped self-managed %s", call); return; } Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall); List<ComponentName> componentsUpdated = new ArrayList<>(); Loading Loading @@ -756,6 +771,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onCallStateChanged(Call call, int oldState, int newState) { if (call.isSelfManaged()) { Log.i(this, "onExternalCallChanged: skipped self-managed %s", call); return; } updateCall(call); } Loading @@ -764,6 +784,11 @@ public final class InCallController extends CallsManagerListenerBase { Call call, ConnectionServiceWrapper oldService, ConnectionServiceWrapper newService) { if (call.isSelfManaged()) { Log.i(this, "onConnectionServiceChanged: skipped self-managed %s", call); return; } updateCall(call); } Loading Loading @@ -809,6 +834,11 @@ public final class InCallController extends CallsManagerListenerBase { @Override public void onIsConferencedChanged(Call call) { if (call.isSelfManaged()) { Log.i(this, "onIsConferencedChanged: skipped self-managed %s", call); return; } Log.d(this, "onIsConferencedChanged %s", call); updateCall(call); } Loading Loading @@ -1103,7 +1133,8 @@ public final class InCallController extends CallsManagerListenerBase { int numCallsSent = 0; for (Call call : calls) { try { if (call.isExternalCall() && !info.isExternalCallsSupported()) { if (call.isSelfManaged() || (call.isExternalCall() && !info.isExternalCallsSupported())) { continue; } Loading
src/com/android/server/telecom/LogUtils.java +1 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ public class LogUtils { public static final String START_CALL_WAITING_TONE = "START_CALL_WAITING_TONE"; public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE"; public static final String START_CONNECTION = "START_CONNECTION"; public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED"; public static final String BIND_CS = "BIND_CS"; public static final String CS_BOUND = "CS_BOUND"; public static final String CONFERENCE_WITH = "CONF_WITH"; Loading