Loading flags/telecom_api_flags.aconfig +12 −0 Original line number Diff line number Diff line Loading @@ -94,3 +94,15 @@ flag { purpose: PURPOSE_FEATURE } } # OWNER=tgunn TARGET=26Q2 flag { name: "call_connected_indicator_preference" is_exported: true namespace: "telecom" description: "Add call connected indicator support for playing a tone or starting a vibration" bug: "146090790" metadata { purpose: PURPOSE_FEATURE } } flags/telecom_call_flags.aconfig +12 −0 Original line number Diff line number Diff line Loading @@ -97,3 +97,15 @@ flag { purpose: PURPOSE_BUGFIX } } # OWNER=tjstuart TARGET=25Q4 flag { name: "echo_abort_transactional_outgoing" namespace: "telecom" description: "Fixes bug where clients are not informed of aborted outgoing calls" bug: "354122175" metadata { purpose: PURPOSE_BUGFIX } } src/com/android/server/telecom/Call.java +7 −2 Original line number Diff line number Diff line Loading @@ -2881,12 +2881,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT || mState == CallState.CONNECTING) { Log.i(this, "disconnect: Aborting call %s", getId()); abort(disconnectionTimeout); if (mFlags.enableCallSequencing()) { disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall( false /* shouldDisconnectUponTimeout */, "disconnect", CallState.DISCONNECTED, CallState.ABORTED); } abort(disconnectionTimeout); } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) { setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED)); Loading Loading @@ -2924,7 +2924,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, if (mCreateConnectionProcessor != null && !mCreateConnectionProcessor.isProcessingComplete()) { mCreateConnectionProcessor.abort(); } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT } else if (mFlags.echoAbortTransactionalOutgoing() && mIsTransactionalCall) { CompletableFuture<Boolean> wasCompleted = mTransactionalService.onDisconnect(this, new DisconnectCause(DisconnectCause.CANCELED)); Log.d(this, "abort: wasTransactionCompleted=[%b", wasCompleted); } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT || mState == CallState.CONNECTING) { if (disconnectionTimeout > 0) { // If the cancelation was from NEW_OUTGOING_CALL with a timeout of > 0 Loading src/com/android/server/telecom/CallConnectedIndicatorSettings.java 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.telecom; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_NONE; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_TONE; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_VIBRATION; import android.content.Context; import android.content.SharedPreferences; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.flags.FeatureFlags; @VisibleForTesting public class CallConnectedIndicatorSettings { private static final String TAG = "CallConnectedIndicatorSettings"; private static final int ALL_SUPPORTED_PREFERENCE = CALL_CONNECTED_INDICATOR_TONE | CALL_CONNECTED_INDICATOR_VIBRATION; private static final String SHARED_PREFERENCES_NAME = "call_connected_indicator_prefs"; private static final String SHARED_PREFERENCES_KEY = "preference_key"; private final Context mContext; private final FeatureFlags mFeatureFlags; private int mCallConnectedIndicator = CALL_CONNECTED_INDICATOR_NONE; public CallConnectedIndicatorSettings(Context context, FeatureFlags flag) { mContext = context; mFeatureFlags = flag; final SharedPreferences prefs = context.getSharedPreferences( SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mCallConnectedIndicator = prefs.getInt(SHARED_PREFERENCES_KEY, CALL_CONNECTED_INDICATOR_NONE); } public synchronized boolean isCallConnectedVibrationEnabled() { return (mCallConnectedIndicator & CALL_CONNECTED_INDICATOR_VIBRATION) == CALL_CONNECTED_INDICATOR_VIBRATION; } public synchronized boolean isCallConnectedToneEnabled() { return (mCallConnectedIndicator & CALL_CONNECTED_INDICATOR_TONE) == CALL_CONNECTED_INDICATOR_TONE; } public synchronized void setCallConnectedIndicatorPreference(int preference) { if ((preference & ~ALL_SUPPORTED_PREFERENCE) > 0) { throw new IllegalArgumentException("Invalid preference " + preference); } mCallConnectedIndicator = preference; final SharedPreferences prefs = mContext.getSharedPreferences( SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); final SharedPreferences.Editor editor = prefs.edit(); editor.putInt(SHARED_PREFERENCES_KEY, mCallConnectedIndicator); editor.commit(); } public synchronized int getCallConnectedIndicatorPreference() { return mCallConnectedIndicator; } } src/com/android/server/telecom/CallsManager.java +18 −1 Original line number Diff line number Diff line Loading @@ -612,6 +612,8 @@ public class CallsManager extends Call.ListenerBase } }; private final CallConnectedIndicatorSettings mCallConnectedIndicatorSettings; /** * Initializes the required Telecom components. */ Loading Loading @@ -751,11 +753,13 @@ public class CallsManager extends Call.ListenerBase mCallEndpointController = callEndpointControllerFactory.create(context, mLock, this); mCallDiagnosticServiceController = callDiagnosticServiceController; mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory); mCallConnectedIndicatorSettings = new CallConnectedIndicatorSettings(context, featureFlags); mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer, ringtoneFactory, vibratorAdapter, new Ringer.VibrationEffectProxy(), mInCallController, mContext.getSystemService(NotificationManager.class), accessibilityManagerAdapter, featureFlags, mAnomalyReporter); accessibilityManagerAdapter, featureFlags, mAnomalyReporter, mCallConnectedIndicatorSettings, asyncTaskExecutor); if (featureFlags.telecomResolveHiddenDependencies()) { // This is now deprecated mCallRecordingTonePlayer = null; Loading Loading @@ -5391,6 +5395,11 @@ public class CallsManager extends Call.ListenerBase stopDtmfTone(call); } // Maybe start vibrating for MO call. if (newState == CallState.ACTIVE && !call.isIncoming() && !call.isUnknown()) { mRinger.startVibratingForOutgoingCallActive(); } // Unfortunately, in the telephony world the radio is king. So if the call notifies // us that the call is in a particular state, we allow it even if it doesn't make // sense (e.g., STATE_ACTIVE -> STATE_RINGING). Loading Loading @@ -7653,4 +7662,12 @@ public class CallsManager extends Call.ListenerBase getPendingAccountSelection() { return mPendingAccountSelection; } public int getCallConnectedIndicatorPreference() { return mCallConnectedIndicatorSettings.getCallConnectedIndicatorPreference(); } public void setCallConnectedIndicatorPreference(int preference) { mCallConnectedIndicatorSettings.setCallConnectedIndicatorPreference(preference); } } Loading
flags/telecom_api_flags.aconfig +12 −0 Original line number Diff line number Diff line Loading @@ -94,3 +94,15 @@ flag { purpose: PURPOSE_FEATURE } } # OWNER=tgunn TARGET=26Q2 flag { name: "call_connected_indicator_preference" is_exported: true namespace: "telecom" description: "Add call connected indicator support for playing a tone or starting a vibration" bug: "146090790" metadata { purpose: PURPOSE_FEATURE } }
flags/telecom_call_flags.aconfig +12 −0 Original line number Diff line number Diff line Loading @@ -97,3 +97,15 @@ flag { purpose: PURPOSE_BUGFIX } } # OWNER=tjstuart TARGET=25Q4 flag { name: "echo_abort_transactional_outgoing" namespace: "telecom" description: "Fixes bug where clients are not informed of aborted outgoing calls" bug: "354122175" metadata { purpose: PURPOSE_BUGFIX } }
src/com/android/server/telecom/Call.java +7 −2 Original line number Diff line number Diff line Loading @@ -2881,12 +2881,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT || mState == CallState.CONNECTING) { Log.i(this, "disconnect: Aborting call %s", getId()); abort(disconnectionTimeout); if (mFlags.enableCallSequencing()) { disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall( false /* shouldDisconnectUponTimeout */, "disconnect", CallState.DISCONNECTED, CallState.ABORTED); } abort(disconnectionTimeout); } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) { if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) { setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED)); Loading Loading @@ -2924,7 +2924,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable, if (mCreateConnectionProcessor != null && !mCreateConnectionProcessor.isProcessingComplete()) { mCreateConnectionProcessor.abort(); } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT } else if (mFlags.echoAbortTransactionalOutgoing() && mIsTransactionalCall) { CompletableFuture<Boolean> wasCompleted = mTransactionalService.onDisconnect(this, new DisconnectCause(DisconnectCause.CANCELED)); Log.d(this, "abort: wasTransactionCompleted=[%b", wasCompleted); } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT || mState == CallState.CONNECTING) { if (disconnectionTimeout > 0) { // If the cancelation was from NEW_OUTGOING_CALL with a timeout of > 0 Loading
src/com/android/server/telecom/CallConnectedIndicatorSettings.java 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.telecom; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_NONE; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_TONE; import static android.telecom.TelecomManager.CALL_CONNECTED_INDICATOR_VIBRATION; import android.content.Context; import android.content.SharedPreferences; import com.android.internal.annotations.VisibleForTesting; import com.android.server.telecom.flags.FeatureFlags; @VisibleForTesting public class CallConnectedIndicatorSettings { private static final String TAG = "CallConnectedIndicatorSettings"; private static final int ALL_SUPPORTED_PREFERENCE = CALL_CONNECTED_INDICATOR_TONE | CALL_CONNECTED_INDICATOR_VIBRATION; private static final String SHARED_PREFERENCES_NAME = "call_connected_indicator_prefs"; private static final String SHARED_PREFERENCES_KEY = "preference_key"; private final Context mContext; private final FeatureFlags mFeatureFlags; private int mCallConnectedIndicator = CALL_CONNECTED_INDICATOR_NONE; public CallConnectedIndicatorSettings(Context context, FeatureFlags flag) { mContext = context; mFeatureFlags = flag; final SharedPreferences prefs = context.getSharedPreferences( SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mCallConnectedIndicator = prefs.getInt(SHARED_PREFERENCES_KEY, CALL_CONNECTED_INDICATOR_NONE); } public synchronized boolean isCallConnectedVibrationEnabled() { return (mCallConnectedIndicator & CALL_CONNECTED_INDICATOR_VIBRATION) == CALL_CONNECTED_INDICATOR_VIBRATION; } public synchronized boolean isCallConnectedToneEnabled() { return (mCallConnectedIndicator & CALL_CONNECTED_INDICATOR_TONE) == CALL_CONNECTED_INDICATOR_TONE; } public synchronized void setCallConnectedIndicatorPreference(int preference) { if ((preference & ~ALL_SUPPORTED_PREFERENCE) > 0) { throw new IllegalArgumentException("Invalid preference " + preference); } mCallConnectedIndicator = preference; final SharedPreferences prefs = mContext.getSharedPreferences( SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); final SharedPreferences.Editor editor = prefs.edit(); editor.putInt(SHARED_PREFERENCES_KEY, mCallConnectedIndicator); editor.commit(); } public synchronized int getCallConnectedIndicatorPreference() { return mCallConnectedIndicator; } }
src/com/android/server/telecom/CallsManager.java +18 −1 Original line number Diff line number Diff line Loading @@ -612,6 +612,8 @@ public class CallsManager extends Call.ListenerBase } }; private final CallConnectedIndicatorSettings mCallConnectedIndicatorSettings; /** * Initializes the required Telecom components. */ Loading Loading @@ -751,11 +753,13 @@ public class CallsManager extends Call.ListenerBase mCallEndpointController = callEndpointControllerFactory.create(context, mLock, this); mCallDiagnosticServiceController = callDiagnosticServiceController; mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory); mCallConnectedIndicatorSettings = new CallConnectedIndicatorSettings(context, featureFlags); mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer, ringtoneFactory, vibratorAdapter, new Ringer.VibrationEffectProxy(), mInCallController, mContext.getSystemService(NotificationManager.class), accessibilityManagerAdapter, featureFlags, mAnomalyReporter); accessibilityManagerAdapter, featureFlags, mAnomalyReporter, mCallConnectedIndicatorSettings, asyncTaskExecutor); if (featureFlags.telecomResolveHiddenDependencies()) { // This is now deprecated mCallRecordingTonePlayer = null; Loading Loading @@ -5391,6 +5395,11 @@ public class CallsManager extends Call.ListenerBase stopDtmfTone(call); } // Maybe start vibrating for MO call. if (newState == CallState.ACTIVE && !call.isIncoming() && !call.isUnknown()) { mRinger.startVibratingForOutgoingCallActive(); } // Unfortunately, in the telephony world the radio is king. So if the call notifies // us that the call is in a particular state, we allow it even if it doesn't make // sense (e.g., STATE_ACTIVE -> STATE_RINGING). Loading Loading @@ -7653,4 +7662,12 @@ public class CallsManager extends Call.ListenerBase getPendingAccountSelection() { return mPendingAccountSelection; } public int getCallConnectedIndicatorPreference() { return mCallConnectedIndicatorSettings.getCallConnectedIndicatorPreference(); } public void setCallConnectedIndicatorPreference(int preference) { mCallConnectedIndicatorSettings.setCallConnectedIndicatorPreference(preference); } }