Loading AndroidManifest.xml +10 −1 Original line number Diff line number Diff line Loading @@ -212,6 +212,8 @@ <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.PROCEED_WITH_CALL" /> <action android:name="com.android.server.telecom.CANCEL_CALL" /> </intent-filter> </receiver> Loading Loading @@ -254,6 +256,14 @@ android:process=":ui"> </activity> <activity android:name=".ui.ConfirmCallDialogActivity" android:configChanges="orientation|screenSize|keyboardHidden" android:excludeFromRecents="true" android:launchMode="singleInstance" android:theme="@style/Theme.Telecomm.Transparent" android:process=":ui"> </activity> <activity android:name=".components.ChangeDefaultDialerDialog" android:label="@string/change_default_dialer_dialog_title" android:excludeFromRecents="true" Loading @@ -265,7 +275,6 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <activity android:name=".testapps.IncomingSelfManagedCallActivity" /> <receiver android:name=".components.PrimaryCallReceiver" android:exported="true" Loading res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -252,4 +252,8 @@ <string name="notification_channel_incoming_call">Incoming calls</string> <!-- Notification channel name for a channel containing missed call notifications. --> <string name="notification_channel_missed_call">Missed calls</string> <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the ongoing call in the app "other_app". --> <string name="alert_outgoing_call">Placing this call will end your <xliff:g id="other_app">%1$s</xliff:g> call.</string> </resources> src/com/android/server/telecom/Call.java +15 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; Loading Loading @@ -310,6 +311,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable { private Bundle mIntentExtras = new Bundle(); /** * The {@link Intent} which originally created this call. Only populated when we are putting a * call into a pending state and need to pick up initiation of the call later. */ private Intent mOriginalCallIntent = null; /** Set of listeners on this call. * * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is Loading Loading @@ -1706,6 +1713,14 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable { mIntentExtras = extras; } public Intent getOriginalCallIntent() { return mOriginalCallIntent; } public void setOriginalCallIntent(Intent intent) { mOriginalCallIntent = intent; } /** * @return the uri of the contact associated with this call. */ Loading src/com/android/server/telecom/CallIntentProcessor.java +23 −18 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ import android.os.Bundle; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.telecom.Connection; import android.telecom.DefaultDialerManager; import android.telecom.Log; import android.telecom.PhoneAccount; Loading Loading @@ -128,8 +127,6 @@ public class CallIntentProcessor { VideoProfile.STATE_AUDIO_ONLY); clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent); // Show the toast to warn user that it is a personal call though initiated in work profile. if (fixedInitiatingUser) { Loading @@ -140,14 +137,23 @@ public class CallIntentProcessor { // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns Call call = callsManager .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser); .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, intent); if (call != null) { sendNewOutgoingCallIntent(context, call, callsManager, intent); } } static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, Intent intent) { // Asynchronous calls should not usually be made inside a BroadcastReceiver because once // onReceive is complete, the BroadcastReceiver's process runs the risk of getting // killed if memory is scarce. However, this is OK here because the entire Telecom // process will be running throughout the duration of the phone call and should never // be killed. final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(), isPrivilegedDialer); Loading @@ -158,7 +164,6 @@ public class CallIntentProcessor { disconnectCallAndShowErrorDialog(context, call, result); } } } /** * If the call is initiated from managed profile but there is no work dialer installed, treat Loading src/com/android/server/telecom/CallsManager.java +142 −19 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.server.telecom.callfiltering.CallScreeningServiceFilter; import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter; import com.android.server.telecom.callfiltering.IncomingCallFilter; import com.android.server.telecom.components.ErrorDialogActivity; import com.android.server.telecom.ui.ConfirmCallDialogActivity; import com.android.server.telecom.ui.IncomingCallNotifier; import java.util.ArrayList; Loading Loading @@ -208,6 +209,12 @@ public class CallsManager extends Call.ListenerBase private final Set<Call> mCalls = Collections.newSetFromMap( new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1)); /** * A pending call is one which requires user-intervention in order to be placed. * Used by {@link #startCallConfirmation(Call)}. */ private Call mPendingCall; /** * The current telecom call ID. Used when creating new instances of {@link Call}. Should * only be accessed using the {@link #getNextCallId()} method which synchronizes on the Loading Loading @@ -949,15 +956,15 @@ public class CallsManager extends Call.ListenerBase * 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 * connection service to use for this call. * @param extras The optional extras Bundle passed with the intent used for the incoming call. * @param initiatingUser {@link UserHandle} of user that place the outgoing call. * @param originalIntent */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras, UserHandle initiatingUser) { UserHandle initiatingUser, Intent originalIntent) { boolean isReusedCall = true; Call call = reuseOutgoingCall(handle); Loading Loading @@ -997,7 +1004,6 @@ public class CallsManager extends Call.ListenerBase } call.setInitiatingUser(initiatingUser); isReusedCall = false; } Loading Loading @@ -1115,9 +1121,15 @@ public class CallsManager extends Call.ListenerBase } setIntentExtrasAndStartTime(call, extras); if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { // Do not add the call if it is a potential MMI code. if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { call.addListener(this); } else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) { // Adding a managed call and there are ongoing self-managed call(s). call.setOriginalCallIntent(originalIntent); startCallConfirmation(call); return null; } else if (!mCalls.contains(call)) { // We check if mCalls already contains the call because we could potentially be reusing // a call which was previously added (See {@link #reuseOutgoingCall}). Loading Loading @@ -1190,23 +1202,11 @@ public class CallsManager extends Call.ListenerBase if (call.isSelfManaged() && !isOutgoingCallPermitted) { notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call); } else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) { Call activeCall = getActiveCall(); CharSequence errorMessage; if (activeCall == null) { // Realistically this shouldn't happen, but best to handle gracefully errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call); } else { errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call, activeCall.getTargetPhoneAccountLabel()); } // Call is managed and there are ongoing self-managed calls. markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR, errorMessage, errorMessage, "Ongoing call in another app.")); markCallAsRemoved(call); markCallDisconnectedDueToSelfManagedCall(call); } else { if (call.isEmergencyCall()) { // Disconnect all self-managed calls to make priority for emergency call. mCalls.stream().filter(c -> c.isSelfManaged()).forEach(c -> c.disconnect()); disconnectSelfManagedCalls(); } call.startCreateConnection(mPhoneAccountRegistrar); Loading Loading @@ -1702,6 +1702,33 @@ public class CallsManager extends Call.ListenerBase } } /** * Given a call, marks the call as disconnected and removes it. Set the error message to * indicate to the user that the call cannot me placed due to an ongoing call in another app. * * Used when there are ongoing self-managed calls and the user tries to make an outgoing managed * call. Called by {@link #startCallConfirmation(Call)} when the user is already confirming an * outgoing call. Realistically this should almost never be called since in practice the user * won't make multiple outgoing calls at the same time. * * @param call The call to mark as disconnected. */ void markCallDisconnectedDueToSelfManagedCall(Call call) { Call activeCall = getActiveCall(); CharSequence errorMessage; if (activeCall == null) { // Realistically this shouldn't happen, but best to handle gracefully errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call); } else { errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call, activeCall.getTargetPhoneAccountLabel()); } // Call is managed and there are ongoing self-managed calls. markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR, errorMessage, errorMessage, "Ongoing call in another app.")); markCallAsRemoved(call); } /** * Cleans up any calls currently associated with the specified connection service when the * service binder disconnects unexpectedly. Loading Loading @@ -2659,6 +2686,97 @@ public class CallsManager extends Call.ListenerBase } } /** * Used to confirm creation of an outgoing call which was marked as pending confirmation in * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}. * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via * {@link ConfirmCallDialogActivity}. * @param callId The call ID of the call to confirm. */ public void confirmPendingCall(String callId) { Log.i(this, "confirmPendingCall: callId=%s", callId); if (mPendingCall != null && mPendingCall.getId().equals(callId)) { Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED); addCall(mPendingCall); // We are going to place the new outgoing call, so disconnect any ongoing self-managed // calls which are ongoing at this time. disconnectSelfManagedCalls(); // Kick of the new outgoing call intent from where it left off prior to confirming the // call. CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this, mPendingCall.getOriginalCallIntent()); mPendingCall = null; } } /** * Used to cancel an outgoing call which was marked as pending confirmation in * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}. * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via * {@link ConfirmCallDialogActivity}. * @param callId The call ID of the call to cancel. */ public void cancelPendingCall(String callId) { Log.i(this, "cancelPendingCall: callId=%s", callId); if (mPendingCall != null && mPendingCall.getId().equals(callId)) { Log.addEvent(mPendingCall, LogUtils.Events.USER_CANCELLED); markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED)); markCallAsRemoved(mPendingCall); mPendingCall = null; } } /** * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)} when * a managed call is added while there are ongoing self-managed calls. Starts * {@link ConfirmCallDialogActivity} to prompt the user to see if they wish to place the * outgoing call or not. * @param call The call to confirm. */ private void startCallConfirmation(Call call) { if (mPendingCall != null) { Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s", mPendingCall.getId(), call.getId()); markCallDisconnectedDueToSelfManagedCall(call); return; } Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION); mPendingCall = call; // Figure out the name of the app in charge of the self-managed call(s). Call selfManagedCall = mCalls.stream() .filter(c -> c.isSelfManaged()) .findFirst() .orElse(null); CharSequence ongoingAppName = ""; if (selfManagedCall != null) { ongoingAppName = selfManagedCall.getTargetPhoneAccountLabel(); } Log.i(this, "startCallConfirmation: callId=%s, ongoingApp=%s", call.getId(), ongoingAppName); Intent confirmIntent = new Intent(mContext, ConfirmCallDialogActivity.class); confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID, call.getId()); confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_ONGOING_APP_NAME, ongoingAppName); confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT); } /** * Disconnects all self-managed calls. */ private void disconnectSelfManagedCalls() { // Disconnect all self-managed calls to make priority for emergency call. // Use Call.disconnect() to command the ConnectionService to disconnect the calls. // CallsManager.markCallAsDisconnected doesn't actually tell the ConnectionService to // disconnect. mCalls.stream() .filter(c -> c.isSelfManaged()) .forEach(c -> c.disconnect()); } /** * Dumps the state of the {@link CallsManager}. * Loading @@ -2675,6 +2793,11 @@ public class CallsManager extends Call.ListenerBase pw.decreaseIndent(); } if (mPendingCall != null) { pw.print("mPendingCall:"); pw.println(mPendingCall.getId()); } if (mCallAudioManager != null) { pw.println("mCallAudioManager:"); pw.increaseIndent(); Loading Loading
AndroidManifest.xml +10 −1 Original line number Diff line number Diff line Loading @@ -212,6 +212,8 @@ <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION" /> <action android:name="com.android.server.telecom.PROCEED_WITH_CALL" /> <action android:name="com.android.server.telecom.CANCEL_CALL" /> </intent-filter> </receiver> Loading Loading @@ -254,6 +256,14 @@ android:process=":ui"> </activity> <activity android:name=".ui.ConfirmCallDialogActivity" android:configChanges="orientation|screenSize|keyboardHidden" android:excludeFromRecents="true" android:launchMode="singleInstance" android:theme="@style/Theme.Telecomm.Transparent" android:process=":ui"> </activity> <activity android:name=".components.ChangeDefaultDialerDialog" android:label="@string/change_default_dialer_dialog_title" android:excludeFromRecents="true" Loading @@ -265,7 +275,6 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <activity android:name=".testapps.IncomingSelfManagedCallActivity" /> <receiver android:name=".components.PrimaryCallReceiver" android:exported="true" Loading
res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -252,4 +252,8 @@ <string name="notification_channel_incoming_call">Incoming calls</string> <!-- Notification channel name for a channel containing missed call notifications. --> <string name="notification_channel_missed_call">Missed calls</string> <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the ongoing call in the app "other_app". --> <string name="alert_outgoing_call">Placing this call will end your <xliff:g id="other_app">%1$s</xliff:g> call.</string> </resources>
src/com/android/server/telecom/Call.java +15 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; Loading Loading @@ -310,6 +311,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable { private Bundle mIntentExtras = new Bundle(); /** * The {@link Intent} which originally created this call. Only populated when we are putting a * call into a pending state and need to pick up initiation of the call later. */ private Intent mOriginalCallIntent = null; /** Set of listeners on this call. * * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is Loading Loading @@ -1706,6 +1713,14 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable { mIntentExtras = extras; } public Intent getOriginalCallIntent() { return mOriginalCallIntent; } public void setOriginalCallIntent(Intent intent) { mOriginalCallIntent = intent; } /** * @return the uri of the contact associated with this call. */ Loading
src/com/android/server/telecom/CallIntentProcessor.java +23 −18 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ import android.os.Bundle; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.telecom.Connection; import android.telecom.DefaultDialerManager; import android.telecom.Log; import android.telecom.PhoneAccount; Loading Loading @@ -128,8 +127,6 @@ public class CallIntentProcessor { VideoProfile.STATE_AUDIO_ONLY); clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent); // Show the toast to warn user that it is a personal call though initiated in work profile. if (fixedInitiatingUser) { Loading @@ -140,14 +137,23 @@ public class CallIntentProcessor { // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns Call call = callsManager .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser); .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, intent); if (call != null) { sendNewOutgoingCallIntent(context, call, callsManager, intent); } } static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, Intent intent) { // Asynchronous calls should not usually be made inside a BroadcastReceiver because once // onReceive is complete, the BroadcastReceiver's process runs the risk of getting // killed if memory is scarce. However, this is OK here because the entire Telecom // process will be running throughout the duration of the phone call and should never // be killed. final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(), isPrivilegedDialer); Loading @@ -158,7 +164,6 @@ public class CallIntentProcessor { disconnectCallAndShowErrorDialog(context, call, result); } } } /** * If the call is initiated from managed profile but there is no work dialer installed, treat Loading
src/com/android/server/telecom/CallsManager.java +142 −19 Original line number Diff line number Diff line Loading @@ -66,6 +66,7 @@ import com.android.server.telecom.callfiltering.CallScreeningServiceFilter; import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter; import com.android.server.telecom.callfiltering.IncomingCallFilter; import com.android.server.telecom.components.ErrorDialogActivity; import com.android.server.telecom.ui.ConfirmCallDialogActivity; import com.android.server.telecom.ui.IncomingCallNotifier; import java.util.ArrayList; Loading Loading @@ -208,6 +209,12 @@ public class CallsManager extends Call.ListenerBase private final Set<Call> mCalls = Collections.newSetFromMap( new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1)); /** * A pending call is one which requires user-intervention in order to be placed. * Used by {@link #startCallConfirmation(Call)}. */ private Call mPendingCall; /** * The current telecom call ID. Used when creating new instances of {@link Call}. Should * only be accessed using the {@link #getNextCallId()} method which synchronizes on the Loading Loading @@ -949,15 +956,15 @@ public class CallsManager extends Call.ListenerBase * 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 * connection service to use for this call. * @param extras The optional extras Bundle passed with the intent used for the incoming call. * @param initiatingUser {@link UserHandle} of user that place the outgoing call. * @param originalIntent */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras, UserHandle initiatingUser) { UserHandle initiatingUser, Intent originalIntent) { boolean isReusedCall = true; Call call = reuseOutgoingCall(handle); Loading Loading @@ -997,7 +1004,6 @@ public class CallsManager extends Call.ListenerBase } call.setInitiatingUser(initiatingUser); isReusedCall = false; } Loading Loading @@ -1115,9 +1121,15 @@ public class CallsManager extends Call.ListenerBase } setIntentExtrasAndStartTime(call, extras); if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { // Do not add the call if it is a potential MMI code. if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { call.addListener(this); } else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) { // Adding a managed call and there are ongoing self-managed call(s). call.setOriginalCallIntent(originalIntent); startCallConfirmation(call); return null; } else if (!mCalls.contains(call)) { // We check if mCalls already contains the call because we could potentially be reusing // a call which was previously added (See {@link #reuseOutgoingCall}). Loading Loading @@ -1190,23 +1202,11 @@ public class CallsManager extends Call.ListenerBase if (call.isSelfManaged() && !isOutgoingCallPermitted) { notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call); } else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) { Call activeCall = getActiveCall(); CharSequence errorMessage; if (activeCall == null) { // Realistically this shouldn't happen, but best to handle gracefully errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call); } else { errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call, activeCall.getTargetPhoneAccountLabel()); } // Call is managed and there are ongoing self-managed calls. markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR, errorMessage, errorMessage, "Ongoing call in another app.")); markCallAsRemoved(call); markCallDisconnectedDueToSelfManagedCall(call); } else { if (call.isEmergencyCall()) { // Disconnect all self-managed calls to make priority for emergency call. mCalls.stream().filter(c -> c.isSelfManaged()).forEach(c -> c.disconnect()); disconnectSelfManagedCalls(); } call.startCreateConnection(mPhoneAccountRegistrar); Loading Loading @@ -1702,6 +1702,33 @@ public class CallsManager extends Call.ListenerBase } } /** * Given a call, marks the call as disconnected and removes it. Set the error message to * indicate to the user that the call cannot me placed due to an ongoing call in another app. * * Used when there are ongoing self-managed calls and the user tries to make an outgoing managed * call. Called by {@link #startCallConfirmation(Call)} when the user is already confirming an * outgoing call. Realistically this should almost never be called since in practice the user * won't make multiple outgoing calls at the same time. * * @param call The call to mark as disconnected. */ void markCallDisconnectedDueToSelfManagedCall(Call call) { Call activeCall = getActiveCall(); CharSequence errorMessage; if (activeCall == null) { // Realistically this shouldn't happen, but best to handle gracefully errorMessage = mContext.getText(R.string.cant_call_due_to_ongoing_unknown_call); } else { errorMessage = mContext.getString(R.string.cant_call_due_to_ongoing_call, activeCall.getTargetPhoneAccountLabel()); } // Call is managed and there are ongoing self-managed calls. markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR, errorMessage, errorMessage, "Ongoing call in another app.")); markCallAsRemoved(call); } /** * Cleans up any calls currently associated with the specified connection service when the * service binder disconnects unexpectedly. Loading Loading @@ -2659,6 +2686,97 @@ public class CallsManager extends Call.ListenerBase } } /** * Used to confirm creation of an outgoing call which was marked as pending confirmation in * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}. * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via * {@link ConfirmCallDialogActivity}. * @param callId The call ID of the call to confirm. */ public void confirmPendingCall(String callId) { Log.i(this, "confirmPendingCall: callId=%s", callId); if (mPendingCall != null && mPendingCall.getId().equals(callId)) { Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED); addCall(mPendingCall); // We are going to place the new outgoing call, so disconnect any ongoing self-managed // calls which are ongoing at this time. disconnectSelfManagedCalls(); // Kick of the new outgoing call intent from where it left off prior to confirming the // call. CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this, mPendingCall.getOriginalCallIntent()); mPendingCall = null; } } /** * Used to cancel an outgoing call which was marked as pending confirmation in * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}. * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via * {@link ConfirmCallDialogActivity}. * @param callId The call ID of the call to cancel. */ public void cancelPendingCall(String callId) { Log.i(this, "cancelPendingCall: callId=%s", callId); if (mPendingCall != null && mPendingCall.getId().equals(callId)) { Log.addEvent(mPendingCall, LogUtils.Events.USER_CANCELLED); markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED)); markCallAsRemoved(mPendingCall); mPendingCall = null; } } /** * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)} when * a managed call is added while there are ongoing self-managed calls. Starts * {@link ConfirmCallDialogActivity} to prompt the user to see if they wish to place the * outgoing call or not. * @param call The call to confirm. */ private void startCallConfirmation(Call call) { if (mPendingCall != null) { Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s", mPendingCall.getId(), call.getId()); markCallDisconnectedDueToSelfManagedCall(call); return; } Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION); mPendingCall = call; // Figure out the name of the app in charge of the self-managed call(s). Call selfManagedCall = mCalls.stream() .filter(c -> c.isSelfManaged()) .findFirst() .orElse(null); CharSequence ongoingAppName = ""; if (selfManagedCall != null) { ongoingAppName = selfManagedCall.getTargetPhoneAccountLabel(); } Log.i(this, "startCallConfirmation: callId=%s, ongoingApp=%s", call.getId(), ongoingAppName); Intent confirmIntent = new Intent(mContext, ConfirmCallDialogActivity.class); confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID, call.getId()); confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_ONGOING_APP_NAME, ongoingAppName); confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT); } /** * Disconnects all self-managed calls. */ private void disconnectSelfManagedCalls() { // Disconnect all self-managed calls to make priority for emergency call. // Use Call.disconnect() to command the ConnectionService to disconnect the calls. // CallsManager.markCallAsDisconnected doesn't actually tell the ConnectionService to // disconnect. mCalls.stream() .filter(c -> c.isSelfManaged()) .forEach(c -> c.disconnect()); } /** * Dumps the state of the {@link CallsManager}. * Loading @@ -2675,6 +2793,11 @@ public class CallsManager extends Call.ListenerBase pw.decreaseIndent(); } if (mPendingCall != null) { pw.print("mPendingCall:"); pw.println(mPendingCall.getId()); } if (mCallAudioManager != null) { pw.println("mCallAudioManager:"); pw.increaseIndent(); Loading