Loading src/com/android/server/telecom/Call.java +31 −3 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ final class Call implements CreateConnectionResponse { void onConnectionManagerPhoneAccountChanged(Call call); void onPhoneAccountChanged(Call call); void onConferenceableCallsChanged(Call call); boolean onCanceledViaNewOutgoingCallBroadcast(Call call); } abstract static class ListenerBase implements Listener { Loading Loading @@ -138,6 +139,10 @@ final class Call implements CreateConnectionResponse { public void onPhoneAccountChanged(Call call) {} @Override public void onConferenceableCallsChanged(Call call) {} @Override public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) { return false; } } private static final OnQueryCompleteListener sCallerInfoQueryListener = Loading Loading @@ -768,17 +773,21 @@ final class Call implements CreateConnectionResponse { } } void disconnect() { disconnect(false); } /** * Attempts to disconnect the call through the connection service. */ void disconnect() { void disconnect(boolean wasViaNewOutgoingCallBroadcaster) { // Track that the call is now locally disconnecting. setLocallyDisconnecting(true); if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT || mState == CallState.CONNECTING) { Log.v(this, "Aborting call %s", this); abort(); abort(wasViaNewOutgoingCallBroadcaster); } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { if (mConnectionService == null) { Log.e(this, new Exception(), "disconnect() request on a call without a" Loading @@ -794,11 +803,30 @@ final class Call implements CreateConnectionResponse { } } void abort() { void abort(boolean wasViaNewOutgoingCallBroadcaster) { if (mCreateConnectionProcessor != null) { mCreateConnectionProcessor.abort(); } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT || mState == CallState.CONNECTING) { if (wasViaNewOutgoingCallBroadcaster) { // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically // destroy the call. Instead, we announce the cancelation and CallsManager handles // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and // then re-dial them quickly using a gateway, allowing the first call to end // causes jank. This timeout allows CallsManager to transition the first call into // the second call so that in-call only ever sees a single call...eliminating the // jank altogether. for (Listener listener : mListeners) { if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) { // The first listener to handle this wins. A return value of true means that // the listener will handle the disconnection process later and so we // should not continue it here. setLocallyDisconnecting(false); return; } } } handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED)); } else { Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING"); Loading src/com/android/server/telecom/CallsManager.java +53 −13 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.CallLog.Calls; import android.telecom.AudioState; import android.telecom.CallState; Loading @@ -37,7 +38,6 @@ import android.telecom.VideoProfile; import android.telephony.TelephonyManager; import com.android.internal.util.IndentingPrintWriter; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; Loading Loading @@ -124,6 +124,9 @@ public final class CallsManager extends Call.ListenerBase { private final PhoneAccountRegistrar mPhoneAccountRegistrar; private final MissedCallNotifier mMissedCallNotifier; private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>(); private final Set<Call> mPendingCallsToDisconnect = new HashSet<>(); /* Handler tied to thread in which CallManager was initialized. */ private final Handler mHandler = new Handler(); /** * The call the user is currently interacting with. This is the call that should have audio Loading Loading @@ -289,6 +292,22 @@ public final class CallsManager extends Call.ListenerBase { } } @Override public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) { mPendingCallsToDisconnect.add(call); mHandler.postDelayed(new Runnable() { @Override public void run() { if (mPendingCallsToDisconnect.remove(call)) { Log.i(this, "Delayed disconnection of call: %s", call); call.disconnect(); } } }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver())); return true; } ImmutableCollection<Call> getCalls() { return ImmutableList.copyOf(mCalls); } Loading Loading @@ -390,18 +409,20 @@ public final class CallsManager extends Call.ListenerBase { call.startCreateConnection(mPhoneAccountRegistrar); } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * * @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. */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { private Call getNewOutgoingCall(Uri handle) { // First check to see if we can reuse any of the calls that are waiting to disconnect. // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information. for (Call pendingCall : mPendingCallsToDisconnect) { if (Objects.equals(pendingCall.getHandle(), handle)) { mPendingCallsToDisconnect.remove(pendingCall); Log.i(this, "Reusing disconnected call %s", pendingCall); return pendingCall; } } // Create a call with original handle. The handle may be changed when the call is attached // to a connection service, but in most cases will remain the same. Call call = new Call( return new Call( mContext, mConnectionServiceRepository, handle, Loading @@ -410,6 +431,18 @@ public final class CallsManager extends Call.ListenerBase { null /* phoneAccountHandle */, false /* isIncoming */, false /* isConference */); } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * * @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. */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { Call call = getNewOutgoingCall(handle); List<PhoneAccountHandle> accounts = mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme()); Loading Loading @@ -444,6 +477,11 @@ public final class CallsManager extends Call.ListenerBase { // a call, or cancel this call altogether. if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) { // just cancel at this point. if (mCalls.contains(call)) { // This call can already exist if it is a reused call, // See {@link #getNewOutgoingCall}. call.disconnect(); } return null; } Loading @@ -463,7 +501,9 @@ public final class CallsManager extends Call.ListenerBase { // Do not add the call if it is a potential MMI code. if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { call.addListener(this); } else { } 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 #getNewOutgoingCall}). addCall(call); } Loading src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java +1 −1 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ class NewOutgoingCallIntentBroadcaster { if (endEarly) { if (mCall != null) { mCall.disconnect(); mCall.disconnect(true /* wasViaNewOutgoingCall */); } return; } Loading src/com/android/server/telecom/Timeouts.java +10 −0 Original line number Diff line number Diff line Loading @@ -52,4 +52,14 @@ public final class Timeouts { public static long getDirectToVoicemailMillis(ContentResolver contentResolver) { return get(contentResolver, "direct_to_voicemail_ms", 500L); } /** * Returns the amount of time to wait before disconnecting a call that was canceled via * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway * to reuse the existing call, preventing the call from causing a start->end->start jank in the * in-call UI. */ public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) { return get(contentResolver, "new_outgoing_call_cancel_ms", 200L); } } Loading
src/com/android/server/telecom/Call.java +31 −3 Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ final class Call implements CreateConnectionResponse { void onConnectionManagerPhoneAccountChanged(Call call); void onPhoneAccountChanged(Call call); void onConferenceableCallsChanged(Call call); boolean onCanceledViaNewOutgoingCallBroadcast(Call call); } abstract static class ListenerBase implements Listener { Loading Loading @@ -138,6 +139,10 @@ final class Call implements CreateConnectionResponse { public void onPhoneAccountChanged(Call call) {} @Override public void onConferenceableCallsChanged(Call call) {} @Override public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) { return false; } } private static final OnQueryCompleteListener sCallerInfoQueryListener = Loading Loading @@ -768,17 +773,21 @@ final class Call implements CreateConnectionResponse { } } void disconnect() { disconnect(false); } /** * Attempts to disconnect the call through the connection service. */ void disconnect() { void disconnect(boolean wasViaNewOutgoingCallBroadcaster) { // Track that the call is now locally disconnecting. setLocallyDisconnecting(true); if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT || mState == CallState.CONNECTING) { Log.v(this, "Aborting call %s", this); abort(); abort(wasViaNewOutgoingCallBroadcaster); } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { if (mConnectionService == null) { Log.e(this, new Exception(), "disconnect() request on a call without a" Loading @@ -794,11 +803,30 @@ final class Call implements CreateConnectionResponse { } } void abort() { void abort(boolean wasViaNewOutgoingCallBroadcaster) { if (mCreateConnectionProcessor != null) { mCreateConnectionProcessor.abort(); } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT || mState == CallState.CONNECTING) { if (wasViaNewOutgoingCallBroadcaster) { // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically // destroy the call. Instead, we announce the cancelation and CallsManager handles // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and // then re-dial them quickly using a gateway, allowing the first call to end // causes jank. This timeout allows CallsManager to transition the first call into // the second call so that in-call only ever sees a single call...eliminating the // jank altogether. for (Listener listener : mListeners) { if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) { // The first listener to handle this wins. A return value of true means that // the listener will handle the disconnection process later and so we // should not continue it here. setLocallyDisconnecting(false); return; } } } handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED)); } else { Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING"); Loading
src/com/android/server/telecom/CallsManager.java +53 −13 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.CallLog.Calls; import android.telecom.AudioState; import android.telecom.CallState; Loading @@ -37,7 +38,6 @@ import android.telecom.VideoProfile; import android.telephony.TelephonyManager; import com.android.internal.util.IndentingPrintWriter; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; Loading Loading @@ -124,6 +124,9 @@ public final class CallsManager extends Call.ListenerBase { private final PhoneAccountRegistrar mPhoneAccountRegistrar; private final MissedCallNotifier mMissedCallNotifier; private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>(); private final Set<Call> mPendingCallsToDisconnect = new HashSet<>(); /* Handler tied to thread in which CallManager was initialized. */ private final Handler mHandler = new Handler(); /** * The call the user is currently interacting with. This is the call that should have audio Loading Loading @@ -289,6 +292,22 @@ public final class CallsManager extends Call.ListenerBase { } } @Override public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) { mPendingCallsToDisconnect.add(call); mHandler.postDelayed(new Runnable() { @Override public void run() { if (mPendingCallsToDisconnect.remove(call)) { Log.i(this, "Delayed disconnection of call: %s", call); call.disconnect(); } } }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver())); return true; } ImmutableCollection<Call> getCalls() { return ImmutableList.copyOf(mCalls); } Loading Loading @@ -390,18 +409,20 @@ public final class CallsManager extends Call.ListenerBase { call.startCreateConnection(mPhoneAccountRegistrar); } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * * @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. */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { private Call getNewOutgoingCall(Uri handle) { // First check to see if we can reuse any of the calls that are waiting to disconnect. // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information. for (Call pendingCall : mPendingCallsToDisconnect) { if (Objects.equals(pendingCall.getHandle(), handle)) { mPendingCallsToDisconnect.remove(pendingCall); Log.i(this, "Reusing disconnected call %s", pendingCall); return pendingCall; } } // Create a call with original handle. The handle may be changed when the call is attached // to a connection service, but in most cases will remain the same. Call call = new Call( return new Call( mContext, mConnectionServiceRepository, handle, Loading @@ -410,6 +431,18 @@ public final class CallsManager extends Call.ListenerBase { null /* phoneAccountHandle */, false /* isIncoming */, false /* isConference */); } /** * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. * * @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. */ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { Call call = getNewOutgoingCall(handle); List<PhoneAccountHandle> accounts = mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme()); Loading Loading @@ -444,6 +477,11 @@ public final class CallsManager extends Call.ListenerBase { // a call, or cancel this call altogether. if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) { // just cancel at this point. if (mCalls.contains(call)) { // This call can already exist if it is a reused call, // See {@link #getNewOutgoingCall}. call.disconnect(); } return null; } Loading @@ -463,7 +501,9 @@ public final class CallsManager extends Call.ListenerBase { // Do not add the call if it is a potential MMI code. if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { call.addListener(this); } else { } 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 #getNewOutgoingCall}). addCall(call); } Loading
src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java +1 −1 Original line number Diff line number Diff line Loading @@ -114,7 +114,7 @@ class NewOutgoingCallIntentBroadcaster { if (endEarly) { if (mCall != null) { mCall.disconnect(); mCall.disconnect(true /* wasViaNewOutgoingCall */); } return; } Loading
src/com/android/server/telecom/Timeouts.java +10 −0 Original line number Diff line number Diff line Loading @@ -52,4 +52,14 @@ public final class Timeouts { public static long getDirectToVoicemailMillis(ContentResolver contentResolver) { return get(contentResolver, "direct_to_voicemail_ms", 500L); } /** * Returns the amount of time to wait before disconnecting a call that was canceled via * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway * to reuse the existing call, preventing the call from causing a start->end->start jank in the * in-call UI. */ public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) { return get(contentResolver, "new_outgoing_call_cancel_ms", 200L); } }