summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml12
-rw-r--r--flags/Android.bp5
-rw-r--r--flags/telecom_bluetoothroutemanager_flags.aconfig9
-rw-r--r--flags/telecom_callaudioroutestatemachine_flags.aconfig23
-rw-r--r--flags/telecom_calllog_flags.aconfig9
-rw-r--r--flags/telecom_calls_manager_flags.aconfig15
-rw-r--r--flags/telecom_work_profile_flags.aconfig8
-rw-r--r--src/com/android/server/telecom/Call.java48
-rw-r--r--src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java20
-rw-r--r--src/com/android/server/telecom/CallAudioRouteStateMachine.java11
-rw-r--r--src/com/android/server/telecom/CallLogManager.java6
-rwxr-xr-xsrc/com/android/server/telecom/CallsManager.java88
-rw-r--r--src/com/android/server/telecom/ConnectionServiceWrapper.java15
-rw-r--r--src/com/android/server/telecom/InCallController.java40
-rw-r--r--src/com/android/server/telecom/UserUtil.java36
-rw-r--r--src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java4
-rw-r--r--src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java13
-rw-r--r--testapps/transactionalVoipApp/res/values-ca/strings.xml2
-rw-r--r--tests/src/com/android/server/telecom/tests/BasicCallTests.java44
-rw-r--r--tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java11
-rw-r--r--tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java27
-rw-r--r--tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java5
-rw-r--r--tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java90
-rw-r--r--tests/src/com/android/server/telecom/tests/CallLogManagerTest.java50
-rw-r--r--tests/src/com/android/server/telecom/tests/CallTest.java65
-rw-r--r--tests/src/com/android/server/telecom/tests/CallsManagerTest.java29
-rw-r--r--tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java5
-rw-r--r--tests/src/com/android/server/telecom/tests/InCallControllerTests.java66
-rw-r--r--tests/src/com/android/server/telecom/tests/InCallServiceFixture.java12
-rw-r--r--tests/src/com/android/server/telecom/tests/MissedInformationTest.java4
-rw-r--r--tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java5
-rw-r--r--tests/src/com/android/server/telecom/tests/RingerTest.java2
-rw-r--r--tests/src/com/android/server/telecom/tests/TelecomSystemTest.java15
-rw-r--r--tests/src/com/android/server/telecom/tests/TransactionTests.java3
34 files changed, 677 insertions, 120 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 28c85e1e7..c6f5e9c69 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -318,18 +318,6 @@
android:exported="false"
android:process=":ui"/>
- <service android:name=".components.BluetoothPhoneService"
- android:singleUser="true"
- android:process="system"
- android:exported="true">
- <intent-filter>
- <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
- </intent-filter>
- <intent-filter>
- <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
- </intent-filter>
- </service>
-
<service android:name=".components.TelecomService"
android:singleUser="true"
android:process="system"
diff --git a/flags/Android.bp b/flags/Android.bp
index 7e2b31d5e..6fa147a86 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -29,10 +29,13 @@ aconfig_declarations {
"telecom_incallservice_flags.aconfig",
"telecom_default_phone_account_flags.aconfig",
"telecom_callaudioroutestatemachine_flags.aconfig",
+ "telecom_calls_manager_flags.aconfig",
"telecom_anomaly_report_flags.aconfig",
"telecom_callaudiomodestatemachine_flags.aconfig",
"telecom_calllog_flags.aconfig",
- "telecom_resolve_hidden_dependencies.aconfig"
+ "telecom_resolve_hidden_dependencies.aconfig",
+ "telecom_bluetoothroutemanager_flags.aconfig",
+ "telecom_work_profile_flags.aconfig"
],
}
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
new file mode 100644
index 000000000..ddd8571c1
--- /dev/null
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_actual_address_to_enter_connecting_state"
+ namespace: "telecom"
+ description: "Fix bugs that may add bluetooth device with null address."
+ bug: "306113816"
+}
+
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index b0f14a422..6f2c7fc7d 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -40,4 +40,25 @@ flag {
namespace: "telecom"
description: "Refactor call audio set/clear communication device and include unsupported routes."
bug: "308968392"
-} \ No newline at end of file
+}
+
+flag {
+ name: "communication_device_protected_by_lock"
+ namespace: "telecom"
+ description: "Protect set/clear communication device operation with lock to avoid race condition."
+ bug: "303001133"
+}
+
+flag {
+ name: "reset_mute_when_entering_quiescent_bt_route"
+ namespace: "telecom"
+ description: "Reset mute state when entering quiescent bluetooth route."
+ bug: "311313250"
+}
+
+flag {
+ name: "update_route_mask_when_bt_connected"
+ namespace: "telecom"
+ description: "Update supported route mask when Bluetooth devices audio connected."
+ bug: "301695370"
+}
diff --git a/flags/telecom_calllog_flags.aconfig b/flags/telecom_calllog_flags.aconfig
index 075e1f364..3ce7b632a 100644
--- a/flags/telecom_calllog_flags.aconfig
+++ b/flags/telecom_calllog_flags.aconfig
@@ -5,4 +5,11 @@ flag {
namespace: "telecom"
description: "log external call if current device is a wearable one"
bug: "292600751"
-} \ No newline at end of file
+}
+
+flag {
+ name: "telecom_skip_log_based_on_extra"
+ namespace: "telecom"
+ description: "skipping logging a call based on passed extra"
+ bug: "295530944"
+}
diff --git a/flags/telecom_calls_manager_flags.aconfig b/flags/telecom_calls_manager_flags.aconfig
new file mode 100644
index 000000000..1a1948039
--- /dev/null
+++ b/flags/telecom_calls_manager_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "use_improved_listener_order"
+ namespace: "telecom"
+ description: "Make InCallController the first listener to trigger"
+ bug: "24244713"
+}
+
+flag {
+ name: "fix_audio_flicker_for_outgoing_calls"
+ namespace: "telecom"
+ description: "This fix ensures the MO calls won't switch from Active to Quite b/c setDialing was not called"
+ bug: "309540769"
+}
diff --git a/flags/telecom_work_profile_flags.aconfig b/flags/telecom_work_profile_flags.aconfig
new file mode 100644
index 000000000..cc78b3083
--- /dev/null
+++ b/flags/telecom_work_profile_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+ name: "work_profile_associated_user"
+ namespace: "telecom"
+ description: "Redefines the associated user for calls in the context of work profile support (U+)"
+ bug: "294699269"
+} \ No newline at end of file
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6e4dc3ce5..7ef2a12a4 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -72,6 +73,7 @@ import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.ui.ToastFactory;
@@ -324,6 +326,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
}
};
+ private final boolean mIsModifyStatePermissionGranted;
/**
* One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
*/
@@ -786,6 +789,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
*/
private CompletableFuture<Boolean> mDisconnectFuture;
+ private FeatureFlags mFlags;
+
/**
* Persists the specified parameters and initializes the new instance.
* @param context The context.
@@ -817,11 +822,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
boolean shouldAttachToExistingConnection,
boolean isConference,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
- isConference, clockProxy, toastFactory);
+ isConference, clockProxy, toastFactory, featureFlags);
}
@@ -841,8 +847,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
boolean shouldAttachToExistingConnection,
boolean isConference,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
+ mFlags = featureFlags;
mId = callId;
mConnectionId = callId;
mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
@@ -873,6 +881,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
mStartRingTime = 0;
mCallStateChangedAtomWriter.setExistingCallCount(callsManager.getCalls().size());
+ mIsModifyStatePermissionGranted =
+ isModifyPhoneStatePermissionGranted(getDelegatePhoneAccountHandle());
}
/**
@@ -892,6 +902,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
* connection, regardless of whether it's incoming or outgoing.
* @param connectTimeMillis The connection time of the call.
* @param clockProxy
+ * @param featureFlags The telecom feature flags.
*/
Call(
String callId,
@@ -910,11 +921,13 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
long connectTimeMillis,
long connectElapsedTimeMillis,
ClockProxy clockProxy,
- ToastFactory toastFactory) {
+ ToastFactory toastFactory,
+ FeatureFlags featureFlags) {
this(callId, context, callsManager, lock, repository,
phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
- shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
+ shouldAttachToExistingConnection, isConference, clockProxy, toastFactory,
+ featureFlags);
mConnectTimeMillis = connectTimeMillis;
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -1766,8 +1779,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
accountHandle.getComponentName().getPackageName(),
mContext.getPackageManager());
// Set the associated user for the call for MT calls based on the target phone account.
- if (isIncoming() && !accountHandle.getUserHandle().equals(mAssociatedUser)) {
- setAssociatedUser(accountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFlags.workProfileAssociatedUser(),
+ mCallsManager.getPhoneAccountRegistrar(), mCallsManager.getCurrentUserHandle(),
+ accountHandle);
+ if (isIncoming() && !associatedUser.equals(mAssociatedUser)) {
+ setAssociatedUser(associatedUser);
}
}
}
@@ -3098,6 +3115,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE));
}
+ if (mExtras.containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+ if (source != SOURCE_CONNECTION_SERVICE || !mIsModifyStatePermissionGranted) {
+ mExtras.remove(TelecomManager.EXTRA_DO_NOT_LOG_CALL);
+ }
+ }
+
// If the change originated from an InCallService, notify the connection service.
if (source == SOURCE_INCALL_SERVICE) {
Log.addEvent(this, LogUtils.Events.ICS_EXTRAS_CHANGED);
@@ -3112,6 +3135,15 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
}
}
+ private boolean isModifyPhoneStatePermissionGranted(PhoneAccountHandle phoneAccountHandle) {
+ if (phoneAccountHandle == null) {
+ return false;
+ }
+ String packageName = phoneAccountHandle.getComponentName().getPackageName();
+ return PackageManager.PERMISSION_GRANTED == mContext.getPackageManager().checkPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ }
+
/**
* Removes extras from the extras bundle associated with this {@link Call}.
*
@@ -4092,7 +4124,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
* @param associatedUser
*/
public void setAssociatedUser(UserHandle associatedUser) {
- Log.i(this, "Setting associated user for call");
+ Log.i(this, "Setting associated user for call: %s", associatedUser);
Preconditions.checkNotNull(associatedUser);
mAssociatedUser = associatedUser;
}
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 38a96eb3e..5fc241446 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -24,10 +24,12 @@ import android.telecom.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.Flags;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Semaphore;
/**
* Helper class used to keep track of the requested communication device within Telecom for audio
@@ -50,6 +52,7 @@ public class CallAudioCommunicationDeviceTracker {
private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
// Keep track of the locally requested BT audio device if set
private String mBtAudioDevice = null;
+ private final Semaphore mLock = new Semaphore(1);
public CallAudioCommunicationDeviceTracker(Context context) {
mAudioManager = context.getSystemService(AudioManager.class);
@@ -94,6 +97,9 @@ public class CallAudioCommunicationDeviceTracker {
*/
public boolean setCommunicationDevice(int audioDeviceType,
BluetoothDevice btDevice) {
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.tryAcquire();
+ }
// There is only one audio device type associated with each type of BT device.
boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
@@ -153,8 +159,16 @@ public class CallAudioCommunicationDeviceTracker {
if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
}
+ } else if (Flags.communicationDeviceProtectedByLock()) {
+ // Clear BT device if it's still stored. Handles race condition for when a non-BT
+ // device is set for communication shortly after a BT (LE) device is set for
+ // communication but the selection hasn't been cleared yet.
+ mBtAudioDevice = null;
}
}
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.release();
+ }
return result;
}
@@ -164,6 +178,9 @@ public class CallAudioCommunicationDeviceTracker {
* @param audioDeviceTypes The supported audio device types for the device.
*/
public void clearCommunicationDevice(int audioDeviceType) {
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.tryAcquire();
+ }
// There is only one audio device type associated with each type of BT device.
boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
@@ -195,6 +212,9 @@ public class CallAudioCommunicationDeviceTracker {
mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
mBtAudioDevice = null;
}
+ if (Flags.communicationDeviceProtectedByLock()) {
+ mLock.release();
+ }
}
private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) {
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index cc5ee05f3..c0bb50e2f 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -17,7 +17,6 @@
package com.android.server.telecom;
-import android.annotation.FlaggedApi;
import android.app.ActivityManager;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
@@ -840,6 +839,9 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi
if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
setBluetoothOn(null);
}
+ if (mFeatureFlags.updateRouteMaskWhenBtConnected()) {
+ mAvailableRoutes |= ROUTE_BLUETOOTH;
+ }
CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
mBluetoothRouteManager.getConnectedDevices());
@@ -945,7 +947,7 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi
mBluetoothRouteManager.disconnectAudio();
} else {
mBluetoothRouteManager.disconnectAudio();
- transitionTo(mQuiescentBluetoothRoute);
+ reinitialize();
}
mCallAudioManager.notifyAudioOperationsComplete();
} else if (msg.arg1 == RINGING_FOCUS
@@ -1071,6 +1073,9 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi
public void enter() {
super.enter();
mHasUserExplicitlyLeftBluetooth = false;
+ if (mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()) {
+ setMuteOn(false);
+ }
updateInternalCallAudioState();
}
@@ -1839,7 +1844,7 @@ public class CallAudioRouteStateMachine extends StateMachine implements CallAudi
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
}
} else {
- processLegacySpeakerCommunicationDevice(on);
+ speakerOn = processLegacySpeakerCommunicationDevice(on);
}
mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
}
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index cfa2eb40a..fc4e05d69 100644
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -170,6 +170,7 @@ public final class CallLogManager extends CallsManagerListenerBase {
* Call was NOT in the "choose account" phase when disconnected
* Call is NOT a conference call which had children (unless it was remotely hosted).
* Call is NOT a child call from a conference which was remotely hosted.
+ * Call has NOT indicated it should be skipped for logging in its extras
* Call is NOT simulating a single party conference.
* Call was NOT explicitly canceled, except for disconnecting from a conference.
* Call is NOT an external call or an external call on watch.
@@ -201,6 +202,11 @@ public final class CallLogManager extends CallsManagerListenerBase {
return false;
}
+ if (mFeatureFlags.telecomSkipLogBasedOnExtra() && call.getExtras() != null
+ && call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+ return false;
+ }
+
// A child call of a conference which was remotely hosted; these didn't originate on this
// device and should not be logged.
if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 7fea8e04f..5db59c4af 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -685,10 +685,15 @@ public class CallsManager extends Call.ListenerBase
mCallStreamingNotification = callStreamingNotification;
mFeatureFlags = featureFlags;
- mListeners.add(mInCallController);
+ if (mFeatureFlags.useImprovedListenerOrder()) {
+ mListeners.add(mInCallController);
+ }
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
+ if (!mFeatureFlags.useImprovedListenerOrder()) {
+ mListeners.add(mInCallController);
+ }
mListeners.add(mCallEndpointController);
mListeners.add(mCallDiagnosticServiceController);
mListeners.add(mCallAudioManager);
@@ -767,6 +772,10 @@ public class CallsManager extends Call.ListenerBase
call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp(
call.getAssociatedUser()));
+ if (!mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+ setCallState(call, callState, "successful outgoing call");
+ }
+
if (!mCalls.contains(call)) {
// Call was not added previously in startOutgoingCall due to it being a potential MMI
// code, so add it now.
@@ -778,11 +787,16 @@ public class CallsManager extends Call.ListenerBase
listener.onConnectionServiceChanged(call, null, call.getConnectionService());
}
- // Allow the ConnectionService to start the call in the active state. This case is helpful
- // for conference calls or meetings that can skip the dialing stage.
- if (callState == CallState.ACTIVE) {
- setCallState(call, callState, "skipping the dialing state and setting active");
- } else {
+ if (mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+ // Allow the ConnectionService to start the call in the active state. This case is
+ // helpful for conference calls or meetings that can skip the dialing stage.
+ if (callState == CallState.ACTIVE) {
+ setCallState(call, callState, "skipping the dialing state and setting active");
+ } else {
+ markCallAsDialing(call);
+ }
+ }
+ else{
markCallAsDialing(call);
}
}
@@ -1468,7 +1482,8 @@ public class CallsManager extends Call.ListenerBase
false /* forceAttachToExistingConnection */,
isConference, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
// Ensure new calls related to self-managed calls/connections are set as such. This will
// be overridden when the actual connection is returned in startCreateConnection, however
// doing this now ensures the logs and any other logic will treat this call as self-managed
@@ -1495,7 +1510,10 @@ public class CallsManager extends Call.ListenerBase
}
}
// Incoming address was set via EXTRA_INCOMING_CALL_ADDRESS above.
- call.setAssociatedUser(phoneAccountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.workProfileAssociatedUser(),
+ getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+ call.setAssociatedUser(associatedUser);
}
if (phoneAccount != null) {
@@ -1615,15 +1633,19 @@ public class CallsManager extends Call.ListenerBase
// Check if the target phone account is possibly in ECBM.
call.setIsInECBM(getEmergencyCallHelper()
.isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
- // If the phone account user profile is paused or the call isn't visible to the secondary/
- // guest user, reject the non-emergency incoming call. When the current user is the admin,
- // we need to allow the calls to go through if the work profile isn't paused. We should
- // always allow emergency calls and also allow non-emergency calls when ECBM is active for
- // the phone account.
- if ((mUserManager.isQuietModeEnabled(call.getAssociatedUser())
- || (!mUserManager.isUserAdmin(mCurrentUserHandle.getIdentifier())
- && !isCallVisibleForUser(call, mCurrentUserHandle)))
- && !call.isEmergencyCall() && !call.isInECBM()) {
+
+ // Check if call is visible to the current user.
+ boolean isCallHiddenFromProfile = !isCallVisibleForUser(call, mCurrentUserHandle);
+ // For admins, we should check if the work profile is paused in order to reject
+ // the call.
+ if (mUserManager.isUserAdmin(mCurrentUserHandle.getIdentifier())) {
+ isCallHiddenFromProfile &= mUserManager.isQuietModeEnabled(
+ call.getAssociatedUser());
+ }
+
+ // We should always allow emergency calls and also allow non-emergency calls when ECBM
+ // is active for the phone account.
+ if (isCallHiddenFromProfile && !call.isEmergencyCall() && !call.isInECBM()) {
Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
phoneAccountHandle.getUserHandle());
call.setMissedReason(USER_MISSED_NOT_RUNNING);
@@ -1696,11 +1718,15 @@ public class CallsManager extends Call.ListenerBase
true /* forceAttachToExistingConnection */,
false, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
call.initAnalytics();
// For unknown calls, base the associated user off of the target phone account handle.
- call.setAssociatedUser(phoneAccountHandle.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.workProfileAssociatedUser(),
+ getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+ call.setAssociatedUser(associatedUser);
setIntentExtrasAndStartTime(call, extras);
call.addListener(this);
notifyStartCreateConnection(call);
@@ -1814,7 +1840,8 @@ public class CallsManager extends Call.ListenerBase
false /* forceAttachToExistingConnection */,
isConference, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
@@ -4221,7 +4248,8 @@ public class CallsManager extends Call.ListenerBase
connectTime,
connectElapsedTime,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
// Unlike connections, conferences are not created first and then notified as create
// connection complete from the CS. They originate from the CS and are reported directly to
@@ -4239,7 +4267,10 @@ public class CallsManager extends Call.ListenerBase
call.setStatusHints(parcelableConference.getStatusHints());
call.putConnectionServiceExtras(parcelableConference.getExtras());
// For conference calls, set the associated user from the target phone account user handle.
- call.setAssociatedUser(phoneAccount.getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.workProfileAssociatedUser(), getPhoneAccountRegistrar(),
+ getCurrentUserHandle(), phoneAccount);
+ call.setAssociatedUser(associatedUser);
// In case this Conference was added via a ConnectionManager, keep track of the original
// Connection ID as created by the originating ConnectionService.
Bundle extras = parcelableConference.getExtras();
@@ -5277,7 +5308,8 @@ public class CallsManager extends Call.ListenerBase
connection.getConnectTimeMillis() /* connectTimeMillis */,
connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
call.initAnalytics();
call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -5292,7 +5324,10 @@ public class CallsManager extends Call.ListenerBase
connection.getCallerDisplayNamePresentation());
// For existing connections, use the phone account user handle to determine the user
// association with the call.
- call.setAssociatedUser(connection.getPhoneAccount().getUserHandle());
+ UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+ mFeatureFlags.workProfileAssociatedUser(), getPhoneAccountRegistrar(),
+ getCurrentUserHandle(), connection.getPhoneAccount());
+ call.setAssociatedUser(associatedUser);
call.addListener(this);
call.putConnectionServiceExtras(connection.getExtras());
@@ -5937,7 +5972,7 @@ public class CallsManager extends Call.ListenerBase
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
- false, mClockProxy, mToastFactory);
+ false, mClockProxy, mToastFactory, mFeatureFlags);
call.initAnalytics();
// Set self-managed and voipAudioMode if destination is self-managed CS
@@ -6144,7 +6179,8 @@ public class CallsManager extends Call.ListenerBase
false /* forceAttachToExistingConnection */,
false, /* isConference */
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
if (fromCall == null || isHandoverInProgress() ||
!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index c11ee6e33..07b048db5 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -525,8 +525,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
.validateAccountIconUserBoundary(icon, callingUserHandle));
}
- if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
if (parcelableConference.getConnectElapsedTimeMillis() != 0
&& mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -941,9 +939,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.aEC", mPackageAbbreviation);
-
- if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
UserHandle userHandle = Binder.getCallingUserHandle();
// Check that the Calling Package matches PhoneAccountHandle's Component Package
PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
@@ -1358,7 +1353,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
private final CallsManager mCallsManager;
private final AppOpsManager mAppOpsManager;
private final Context mContext;
- public boolean mIsRemoteConnectionService = false;
private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
@@ -2548,13 +2542,13 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
private void logIncoming(String msg, Object... params) {
// Keep these as debug; the incoming logging is traced on a package level through the
// session logging.
- Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]:"
- + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+ Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+ + msg, params);
}
private void logOutgoing(String msg, Object... params) {
- Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]:"
- + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+ Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+ + msg, params);
}
private void queryRemoteConnectionServices(final UserHandle userHandle,
@@ -2581,7 +2575,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
handle.getComponentName(), handle.getUserHandle());
if (service != null && service != this) {
- service.mIsRemoteConnectionService = true;
simServices.add(service);
} else {
// This is unexpected, normally PhoneAccounts with CAPABILITY_CALL_PROVIDER are not
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 184eaddec..1aee25c75 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -82,6 +82,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -1406,17 +1407,31 @@ public class InCallController extends CallsManagerListenerBase implements
@Override
public void onCallRemoved(Call call) {
Log.i(this, "onCallRemoved: %s", call);
- if (mCallsManager.getCalls().isEmpty()) {
+ // Instead of checking if there are no active calls, we should check if there any calls with
+ // the same associated user returned from getUserFromCall. For instance, it's possible to
+ // have calls coexist on the personal profile and work profile, in which case, we would only
+ // remove the ICS connection for the user associated with the call to be disconnected.
+ UserHandle userFromCall = getUserFromCall(call);
+ Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+ .filter((c) -> getUserFromCall(c).equals(userFromCall));
+ boolean isCallCountZero = mFeatureFlags.workProfileAssociatedUser()
+ ? callsAssociatedWithUserFromCall.count() == 0
+ : mCallsManager.getCalls().isEmpty();
+ if (isCallCountZero) {
/** Let's add a 2 second delay before we send unbind to the services to hopefully
* give them enough time to process all the pending messages.
*/
mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
@Override
public void loggedRun() {
- // Check again to make sure there are no active calls.
- if (mCallsManager.getCalls().isEmpty()) {
- unbindFromServices(getUserFromCall(call));
-
+ // Check again to make sure there are no active calls for the associated user.
+ Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+ .filter((c) -> getUserFromCall(c).equals(userFromCall));
+ boolean isCallCountZero = mFeatureFlags.workProfileAssociatedUser()
+ ? callsAssociatedWithUserFromCall.count() == 0
+ : mCallsManager.getCalls().isEmpty();
+ if (isCallCountZero) {
+ unbindFromServices(userFromCall);
mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
}
}
@@ -1832,6 +1847,7 @@ public class InCallController extends CallsManagerListenerBase implements
* Unbinds an existing bound connection to the in-call app.
*/
public void unbindFromServices(UserHandle userHandle) {
+ Log.i(this, "Unbinding from services for user %s", userHandle);
try {
mContext.unregisterReceiver(mPackageChangedReceiver);
} catch (IllegalArgumentException e) {
@@ -2320,7 +2336,9 @@ public class InCallController extends CallsManagerListenerBase implements
}
// Upon successful connection, send the state of the world to the service.
- List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
+ List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls().stream().filter(
+ call -> getUserFromCall(call).equals(userHandle))
+ .collect(Collectors.toUnmodifiableList()));
Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
"calls", calls.size(), info.getComponentName());
int numCallsSent = 0;
@@ -2463,6 +2481,9 @@ public class InCallController extends CallsManagerListenerBase implements
}
}
Log.i(this, "Components updated: %s", componentsUpdated);
+ } else {
+ Log.i(this,
+ "Unable to update call. InCallService not found for user: %s", userFromCall);
}
}
@@ -2901,8 +2922,11 @@ public class InCallController extends CallsManagerListenerBase implements
} else {
UserHandle userFromCall = call.getAssociatedUser();
UserManager userManager = mContext.getSystemService(UserManager.class);
- // Emergency call should never be blocked, so if the user associated with call is in
- // quite mode, use the primary user for the emergency call.
+ // Emergency call should never be blocked, so if the user associated with the target
+ // phone account handle user is in quiet mode, use the current user for the ecall.
+ // Note, that this only applies to incoming calls that are received on assigned
+ // sims (i.e. work sim), where the associated user would be the target phone account
+ // handle user.
if ((call.isEmergencyCall() || call.isInECBM())
&& (userManager.isQuietModeEnabled(userFromCall)
// We should also account for secondary/guest users where the profile may not
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
index d0a561ad9..670ad3438 100644
--- a/src/com/android/server/telecom/UserUtil.java
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -24,8 +24,11 @@ import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.flags.FeatureFlags;
public final class UserUtil {
@@ -99,4 +102,37 @@ public final class UserUtil {
}
return false;
}
+
+ /**
+ * Gets the associated user for the given call. Note: this is applicable to all calls except
+ * outgoing calls as the associated user is already based off of the user placing the
+ * call.
+ *
+ * @param phoneAccountRegistrar
+ * @param currentUser Current user profile (this can either be the admin or a secondary/guest
+ * user). Note that work profile users fall under the admin user.
+ * @param targetPhoneAccount The phone account to retrieve the {@link UserHandle} from.
+ * @return current user if it isn't the admin or if the work profile is paused for the target
+ * phone account handle user, otherwise return the target phone account handle user. If the
+ * flag is disabled, return the legacy {@link UserHandle}.
+ */
+ public static UserHandle getAssociatedUserForCall(boolean isAssociatedUserFlagEnabled,
+ PhoneAccountRegistrar phoneAccountRegistrar, UserHandle currentUser,
+ PhoneAccountHandle targetPhoneAccount) {
+ if (!isAssociatedUserFlagEnabled) {
+ return targetPhoneAccount.getUserHandle();
+ }
+ // For multi-user phone accounts, associate the call with the profile receiving/placing
+ // the call. For SIM accounts (that are assigned to specific users), the user association
+ // will be placed on the target phone account handle user.
+ PhoneAccount account = phoneAccountRegistrar.getPhoneAccountUnchecked(targetPhoneAccount);
+ if (account != null) {
+ return account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)
+ ? currentUser
+ : targetPhoneAccount.getUserHandle();
+ }
+ // If target phone account handle is null or account cannot be found,
+ // return the current user.
+ return currentUser;
+ }
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index c5e56b377..9ae58b33d 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -740,9 +740,7 @@ public class BluetoothDeviceManager {
}
public boolean isInbandRingingEnabled() {
- // Get the inband ringing enabled status of expected BT device to route call audio instead
- // of using the address of currently connected device.
- BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
+ BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
if (mBluetoothLeAudioService == null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 6cf6ae432..644d9aeda 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -393,8 +393,13 @@ public class BluetoothRouteManager extends StateMachine {
String actualAddress = connectBtAudio(address,
true /* switchingBtDevices*/);
if (actualAddress != null) {
- transitionTo(getConnectingStateForAddress(address,
- "AudioConnected/CONNECT_BT"));
+ if (mFeatureFlags.useActualAddressToEnterConnectingState()) {
+ transitionTo(getConnectingStateForAddress(actualAddress,
+ "AudioConnected/CONNECT_BT"));
+ } else {
+ transitionTo(getConnectingStateForAddress(address,
+ "AudioConnected/CONNECT_BT"));
+ }
} else {
Log.w(LOG_TAG, "Tried to connect to %s but failed" +
" to connect to any BT device.", (String) args.arg2);
@@ -663,10 +668,6 @@ public class BluetoothRouteManager extends StateMachine {
}
}
- public BluetoothDevice getMostRecentlyReportedActiveDevice() {
- return mMostRecentlyReportedActiveDevice;
- }
-
public boolean hasBtActiveDevice() {
return mLeAudioActiveDeviceCache != null ||
mHearingAidActiveDeviceCache != null ||
diff --git a/testapps/transactionalVoipApp/res/values-ca/strings.xml b/testapps/transactionalVoipApp/res/values-ca/strings.xml
index 06f165569..550044425 100644
--- a/testapps/transactionalVoipApp/res/values-ca/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -31,7 +31,7 @@
<string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
<string name="request_speaker_endpoint" msgid="1033259535289845405">"Altaveu"</string>
<string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
- <string name="start_stream" msgid="3567634786280097431">"inicia la reproducció en continu"</string>
+ <string name="start_stream" msgid="3567634786280097431">"inicia la reproducció en línia"</string>
<string name="crash_app" msgid="2548690390730057704">"llança una excepció"</string>
<string name="update_notification" msgid="8677916482672588779">"actualitza la notificació a l\'estil de trucada en curs"</string>
</resources>
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 14065ca30..d2937e234 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -69,6 +69,8 @@ import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.telecom.IInCallAdapter;
+import com.android.server.telecom.InCallController;
+
import android.telecom.CallerInfo;
import com.google.common.base.Predicate;
@@ -624,6 +626,48 @@ public class BasicCallTests extends TelecomSystemTest {
@LargeTest
@Test
+ public void testIncomingThenOutgoingCalls_AssociatedUsersNotEqual() throws Exception {
+ when(mFeatureFlags.workProfileAssociatedUser()).thenReturn(true);
+ InCallServiceFixture.setIgnoreOverrideAdapterFlag(true);
+
+ // Receive incoming call via mPhoneAccountMultiUser
+ IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
+ mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+ // Make outgoing call on mPhoneAccountMultiUser (unassociated sim to simulate guest/
+ // secondary user scenario where both MO/MT calls exist).
+ IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+ mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+
+ // Outgoing call should be on hold while incoming call is made active
+ mConnectionServiceFixtureA.mConnectionById.get(incoming.mConnectionId).state =
+ Connection.STATE_HOLDING;
+
+ // Swap calls and verify that outgoing call is now the active call while the incoming call
+ // is the held call.
+ mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+ assertEquals(Call.STATE_HOLDING,
+ mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+ assertEquals(Call.STATE_ACTIVE,
+ mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+
+ // Ensure no issues with call disconnect.
+ mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
+ mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+ InCallServiceFixture.setIgnoreOverrideAdapterFlag(false);
+ }
+
+ @LargeTest
+ @Test
public void testAudioManagerOperations() throws Exception {
AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 86d2beefb..977cef9b8 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -739,14 +739,19 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
when(mBluetoothLeAudio.isInbandRingtoneEnabled(1)).thenReturn(true);
when(mBluetoothLeAudio.getGroupId(eq(device3))).thenReturn(1);
receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+ BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+ receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
leAudioCallbacksTest.getValue().onGroupNodeAdded(device3, 1);
+ when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
when(mRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(device3);
when(mRouteManager.isCachedLeAudioDevice(eq(device3))).thenReturn(true);
- when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
- when(mRouteManager.getMostRecentlyReportedActiveDevice()).thenReturn(device3);
- assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
+ assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 3ed96a07c..ee59cb8e7 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -185,6 +185,33 @@ public class BluetoothRouteManagerTest extends TelecomTestCase {
sm.quitNow();
}
+ @SmallTest
+ @Test
+ public void testConnectBtWithoutAddress() {
+ when(mFeatureFlags.useActualAddressToEnterConnectingState()).thenReturn(true);
+ BluetoothRouteManager sm = setupStateMachine(
+ BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
+ setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
+ null);
+ when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+ nullable(ContentResolver.class))).thenReturn(0L);
+ when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
+ executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, null);
+ // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
+ // so wait twice for the retry attempt, again to make sure there are only three attempts,
+ // and once more for good luck.
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ verifyConnectionAttempt(DEVICE1, 1);
+ assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ + ":" + DEVICE1.getAddress(),
+ sm.getCurrentState().getName());
+ sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
+ sm.quitNow();
+ }
+
private BluetoothRouteManager setupStateMachine(String initialState,
BluetoothDevice initialDevice) {
resetMocks();
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 7e197fe65..86d24f96f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.net.Uri;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -123,6 +124,7 @@ public class CallAnomalyWatchdogTest extends TelecomTestCase {
mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
}
@Override
@@ -862,6 +864,7 @@ public class CallAnomalyWatchdogTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
} \ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 19dfe838b..1fa14a559 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -883,16 +883,6 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase {
verify(mockBluetoothRouteManager, atLeastOnce())
.connectBluetoothAudio(eq(bluetoothDevice1.getAddress()));
assertTrue(stateMachine.isInActiveState());
-
- // Switch to inactive, pretending that the call disconnected.
- stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
- CallAudioRouteStateMachine.NO_FOCUS);
- waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
-
- // Make sure that we've successfully switched to the quiescent BT route
- assertEquals(CallAudioState.ROUTE_BLUETOOTH,
- stateMachine.getCurrentCallAudioState().getRoute());
- assertFalse(stateMachine.isInActiveState());
}
@SmallTest
@@ -1238,6 +1228,86 @@ public class CallAudioRouteStateMachineTest extends TelecomTestCase {
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
+ @SmallTest
+ @Test
+ public void testQuiescentBluetoothRouteResetMute() {
+ when(mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ stateMachine.initialize(initState);
+
+ // Switch to active and mute
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.ACTIVE_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ assertTrue(stateMachine.isInActiveState());
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.MUTE_ON);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedState = new CallAudioState(true,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.NO_FOCUS);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+ expectedState = new CallAudioState(false,
+ CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+ // TODO: Re-enable this part of the test; this is now failing because we have to
+ // revert ag/23783145.
+ // assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ @SmallTest
+ @Test
+ public void testSupportRouteMaskUpdateWhenBtAudioConnected() {
+ when(mFeatureFlags.updateRouteMaskWhenBtConnected()).thenReturn(true);
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */,
+ mCommunicationDeviceTracker,
+ mFeatureFlags);
+ stateMachine.setCallAudioManager(mockCallAudioManager);
+
+ CallAudioState initState = new CallAudioState(false,
+ CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+ waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
private void initializationTestHelper(CallAudioState expectedState,
int earpieceControl) {
when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 83d10434f..c09d138d5 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -967,6 +967,56 @@ public class CallLogManagerTest extends TelecomTestCase {
@SmallTest
@Test
+ public void testDoNotLogCallExtra() {
+ when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(true);
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.LOCAL, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ when(fakeCall.getExtras()).thenReturn(extras);
+
+ assertFalse(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+ false /* isCanceled */));
+ }
+
+ @SmallTest
+ @Test
+ public void testIgnoresDoNotLogCallExtra_whenFlagDisabled() {
+ when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(false);
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.LOCAL, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ VIA_NUMBER_STRING, // viaNumber
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ Bundle extras = new Bundle();
+ extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ when(fakeCall.getExtras()).thenReturn(extras);
+
+ assertTrue(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+ false /* isCanceled */));
+ }
+
+ @SmallTest
+ @Test
public void testDoNotLogConferenceWithChildren() {
Call fakeCall = makeFakeCall(
DisconnectCause.LOCAL, // disconnectCauseCode
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 997e7dd2a..7a773748a 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -21,7 +21,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
@@ -34,10 +33,12 @@ import static org.mockito.Mockito.verify;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.UserHandle;
import android.telecom.CallAttributes;
import android.telecom.CallerInfo;
import android.telecom.Connection;
@@ -117,6 +118,7 @@ public class CallTest extends TelecomTestCase {
doReturn(new ComponentName(mContext, CallTest.class))
.when(mMockConnectionService).getComponentName();
doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+ doReturn(UserHandle.CURRENT).when(mMockCallsManager).getCurrentUserHandle();
}
@After
@@ -200,7 +202,8 @@ public class CallTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
// To start with connection creation isn't complete.
assertFalse(call.isCreateConnectionComplete());
@@ -338,7 +341,8 @@ public class CallTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
assertFalse(call.wasDndCheckComputedForCall());
assertFalse(call.isCallSuppressedByDoNotDisturb());
@@ -364,7 +368,8 @@ public class CallTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
assertNull(call.getConnectionServiceWrapper());
assertFalse(call.isTransactionalCall());
@@ -394,7 +399,8 @@ public class CallTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
true /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
// setup
call.setIsTransactionalCall(true);
@@ -728,6 +734,52 @@ public class CallTest extends TelecomTestCase {
}));
}
+ @Test
+ @SmallTest
+ public void testExcludesInCallServiceFromDoNotLogCallExtra() {
+ Call call = createCall("any");
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+
+ call.putInCallServiceExtras(extra, "packageName");
+
+ assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
+ @Test
+ @SmallTest
+ public void testExcludesConnectionServiceWithoutModifyStatePermissionFromDoNotLogCallExtra() {
+ PackageManager packageManager = mContext.getPackageManager();
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(packageManager)
+ .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ Call call = createCall("any");
+
+ call.putConnectionServiceExtras(extra);
+
+ assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
+ @Test
+ @SmallTest
+ public void testDoesNotExcludeConnectionServiceWithModifyStatePermissionFromDoNotLogCallExtra() {
+ String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+ Bundle extra = new Bundle();
+ extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+ PackageManager packageManager = mContext.getPackageManager();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(packageManager)
+ .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+ Call call = createCall("any");
+
+ call.putConnectionServiceExtras(extra);
+
+ assertTrue(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+ }
+
private Call createCall(String id) {
return createCall(id, Call.CALL_DIRECTION_UNDEFINED);
}
@@ -748,6 +800,7 @@ public class CallTest extends TelecomTestCase {
false,
false,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index fe94da4f3..649f435f3 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -415,7 +415,8 @@ public class CallsManagerTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
ongoingCall.setState(CallState.ACTIVE, "just cuz");
return ongoingCall;
}
@@ -2559,6 +2560,7 @@ public class CallsManagerTest extends TelecomTestCase {
@SmallTest
@Test
public void testOutgoingCallStateIsSetToAPreviousStateAndIgnored() {
+ when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
Call outgoingCall = addSpyCall(CallState.CONNECTING);
mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.NEW);
verify(outgoingCall, never()).setState(eq(CallState.NEW), any());
@@ -2571,6 +2573,7 @@ public class CallsManagerTest extends TelecomTestCase {
@SmallTest
@Test
public void testOutgoingCallStateCanAvoidDialingAndGoStraightToActive() {
+ when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
Call outgoingCall = addSpyCall(CallState.CONNECTING);
mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.ACTIVE);
verify(outgoingCall, never()).setState(eq(CallState.DIALING), any());
@@ -2586,9 +2589,7 @@ public class CallsManagerTest extends TelecomTestCase {
WORK_HANDLE.getUserHandle(), service);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle newUser = new UserHandle(11);
- when(mCallsManager.getCurrentUserHandle()).thenReturn(newUser);
- when(um.isUserAdmin(eq(newUser.getIdentifier()))).thenReturn(false);
+ when(um.isUserAdmin(anyInt())).thenReturn(false);
when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
.thenReturn(WORK_ACCOUNT);
@@ -2604,14 +2605,17 @@ public class CallsManagerTest extends TelecomTestCase {
@Test
public void testRejectIncomingCallOnPAHInactive_ProfilePaused() throws Exception {
ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
- doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
- mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
- SIM_2_HANDLE.getUserHandle(), service);
+ doReturn(WORK_HANDLE.getComponentName()).when(service).getComponentName();
+ mCallsManager.addConnectionServiceRepositoryCache(WORK_HANDLE.getComponentName(),
+ WORK_HANDLE.getUserHandle(), service);
UserManager um = mContext.getSystemService(UserManager.class);
- when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+ when(um.isUserAdmin(anyInt())).thenReturn(true);
+ when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(true);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
+ .thenReturn(WORK_ACCOUNT);
Call newCall = mCallsManager.processIncomingCallIntent(
- SIM_2_HANDLE, new Bundle(), false);
+ WORK_HANDLE, new Bundle(), false);
verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
assertFalse(newCall.isInECBM());
@@ -2648,9 +2652,7 @@ public class CallsManagerTest extends TelecomTestCase {
when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(WORK_HANDLE)))
.thenReturn(true);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle newUser = new UserHandle(11);
- when(mCallsManager.getCurrentUserHandle()).thenReturn(newUser);
- when(um.isUserAdmin(eq(newUser.getIdentifier()))).thenReturn(false);
+ when(um.isUserAdmin(anyInt())).thenReturn(false);
when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
.thenReturn(WORK_ACCOUNT);
@@ -3427,7 +3429,8 @@ public class CallsManagerTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
ongoingCall.setState(initialState, "just cuz");
if (targetPhoneAccount == SELF_MANAGED_HANDLE
|| targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
index 3cb819672..c63a3d5bf 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -35,6 +35,7 @@ import android.content.ComponentName;
import android.net.Uri;
import android.os.BugreportManager;
import android.os.DropBoxManager;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -128,6 +129,7 @@ public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
mTimeouts, mDbm, Runnable::run, mClockProxy);
@@ -171,7 +173,8 @@ public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mMockClockProxy,
- mMockToastProxy);
+ mMockToastProxy,
+ mFeatureFlags);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 1b5677ee7..cd8431a1a 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -141,6 +141,8 @@ public class InCallControllerTests extends TelecomTestCase {
@Mock PackageManager mMockPackageManager;
@Mock PermissionCheckerManager mMockPermissionCheckerManager;
@Mock Call mMockCall;
+ @Mock Call mMockSystemCall1;
+ @Mock Call mMockSystemCall2;
@Mock Resources mMockResources;
@Mock AppOpsManager mMockAppOpsManager;
@Mock MockContext mMockContext;
@@ -586,6 +588,7 @@ public class InCallControllerTests extends TelecomTestCase {
when(mMockCall.isEmergencyCall()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
@@ -615,6 +618,7 @@ public class InCallControllerTests extends TelecomTestCase {
when(mMockCall.isInECBM()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
@@ -645,6 +649,7 @@ public class InCallControllerTests extends TelecomTestCase {
when(mMockCall.isInECBM()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockContext.getSystemService(eq(UserManager.class)))
.thenReturn(mMockUserManager);
when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
@@ -1837,6 +1842,67 @@ public class InCallControllerTests extends TelecomTestCase {
assertNull(mInCallController.getInCallServiceConnections().get(testUser));
}
+ @Test
+ public void testRemoveAllServiceConnections_MultiUser() throws Exception {
+ when(mFeatureFlags.workProfileAssociatedUser()).thenReturn(true);
+ setupMocks(false /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ UserHandle workUser = new UserHandle(12);
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+ when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+ when(mMockCall.getAssociatedUser()).thenReturn(workUser);
+ setupFakeSystemCall(mMockSystemCall1, 1);
+ setupFakeSystemCall(mMockSystemCall2, 2);
+
+ // Add "work" call to service. The mapping should've been inserted
+ // with the workUser as the key.
+ mInCallController.onCallAdded(mMockCall);
+ // Add system call to service. The mapping should've been
+ // inserted with the system user as the key.
+ mInCallController.onCallAdded(mMockSystemCall1);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ // Make sure we bound to the system call as well as the work call.
+ verify(mMockContext, times(2)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(UserHandle.CURRENT));
+ assertTrue(mInCallController.getInCallServiceConnections().containsKey(workUser));
+ assertTrue(mInCallController.getInCallServiceConnections().containsKey(UserHandle.SYSTEM));
+
+ // Remove the work call. This leverages getUserFromCall to remove the ICS mapping.
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall1));
+ mInCallController.onCallRemoved(mMockCall);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Verify that the mapping was properly removed.
+ assertNull(mInCallController.getInCallServiceConnections().get(workUser));
+ // Verify mapping for system user is still present.
+ assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+
+ // Add another system call
+ mInCallController.onCallAdded(mMockSystemCall2);
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall2));
+ // Remove first system call and verify that mapping is present
+ mInCallController.onCallRemoved(mMockSystemCall1);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Verify mapping for system user is still present.
+ assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+ // Remove last system call and verify that connection isn't present in ICS mapping.
+ when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+ mInCallController.onCallRemoved(mMockSystemCall2);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ assertNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+ }
+
+ private void setupFakeSystemCall(@Mock Call call, int id) {
+ when(call.getAssociatedUser()).thenReturn(UserHandle.SYSTEM);
+ when(call.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(call.getAnalytics()).thenReturn(new Analytics.CallInfo());
+ when(call.getId()).thenReturn("TC@" + id);
+ }
+
private void setupMocksForWorkProfileTest() {
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 88b5bb52a..39381e6ca 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telecom.IInCallService;
@@ -39,7 +40,7 @@ import java.util.concurrent.TimeUnit;
* Controls a test {@link IInCallService} as would be provided by an InCall UI on a system.
*/
public class InCallServiceFixture implements TestFixture<IInCallService> {
-
+ public static boolean sIgnoreOverrideAdapterFlag = false;
public String mLatestCallId;
public IInCallAdapter mInCallAdapter;
public CallAudioState mCallAudioState;
@@ -53,10 +54,17 @@ public class InCallServiceFixture implements TestFixture<IInCallService> {
public CountDownLatch mUpdateCallLock = new CountDownLatch(1);
public CountDownLatch mAddCallLock = new CountDownLatch(1);
+ @VisibleForTesting
+ public static void setIgnoreOverrideAdapterFlag(boolean flag) {
+ sIgnoreOverrideAdapterFlag = flag;
+ }
+
public class FakeInCallService extends IInCallService.Stub {
@Override
public void setInCallAdapter(IInCallAdapter inCallAdapter) throws RemoteException {
- if (mInCallAdapter != null && inCallAdapter != null) {
+ // sIgnoreOverrideAdapterFlag is being used to verify a scenario where the InCallAdapter
+ // gets set twice (secondary user places MO/MT call).
+ if (mInCallAdapter != null && inCallAdapter != null && !sIgnoreOverrideAdapterFlag) {
throw new RuntimeException("Adapter is already set");
}
if (mInCallAdapter == null && inCallAdapter == null) {
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index 753c84733..f28966edd 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -153,6 +153,8 @@ public class MissedInformationTest extends TelecomSystemTest {
setUpEmergencyCall();
when(mEmergencyCall.getAssociatedUser()).
thenReturn(mPhoneAccountA0.getAccountHandle().getUserHandle());
+ when(mEmergencyCall.getTargetPhoneAccount())
+ .thenReturn(mPhoneAccountA0.getAccountHandle());
mCallsManager.addCall(mEmergencyCall);
assertTrue(mCallsManager.isInEmergencyCall());
@@ -418,7 +420,7 @@ public class MissedInformationTest extends TelecomSystemTest {
null, mCallsManager.getPhoneNumberUtilsAdapter(), null,
null, null, mPhoneAccountA0.getAccountHandle(),
Call.CALL_DIRECTION_INCOMING, false, false,
- mClockProxy, null));
+ mClockProxy, null, mFeatureFlags));
doReturn(1L).when(mIncomingCall).getStartRingTime();
doAnswer((x) -> {
mCountDownLatch.countDown();
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index fed80847f..57c61915b 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -13,6 +13,7 @@ import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.telecom.Connection;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
@@ -57,6 +58,7 @@ public class ParcelableCallUtilsTest extends TelecomTestCase {
when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any())).thenReturn(null);
when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
.thenReturn(false);
@@ -75,7 +77,8 @@ public class ParcelableCallUtilsTest extends TelecomTestCase {
false /* shouldAttachToExistingConnection */,
false /* isConference */,
mClockProxy /* ClockProxy */,
- mToastProxy);
+ mToastProxy,
+ mFeatureFlags);
}
@Override
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index baa4f901d..985438409 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -147,6 +147,8 @@ public class RingerTest extends TelecomTestCase {
when(mockCall2.getState()).thenReturn(CallState.RINGING);
when(mockCall1.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
when(mockCall2.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+ when(mockCall1.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mockCall2.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
// Set BT active state in tests to ensure that we do not end up blocking tests for 1 sec
// waiting for BT to connect in unit tests by default.
asyncRingtonePlayer.updateBtActiveState(true);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 33b00688c..aa2cf5662 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -327,6 +327,20 @@ public class TelecomSystemTest extends TelecomTestCase{
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
.build();
+ final PhoneAccount mPhoneAccountMultiUser =
+ PhoneAccount.builder(
+ new PhoneAccountHandle(
+ mConnectionServiceComponentNameA,
+ "id MU", UserHandle.of(12)),
+ "Phone account service MU")
+ .addSupportedUriScheme("tel")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+ PhoneAccount.CAPABILITY_VIDEO_CALLING |
+ PhoneAccount.CAPABILITY_MULTI_USER)
+ .build();
+
ConnectionServiceFixture mConnectionServiceFixtureA;
ConnectionServiceFixture mConnectionServiceFixtureB;
Timeouts.Adapter mTimeoutsAdapter;
@@ -601,6 +615,7 @@ public class TelecomSystemTest extends TelecomTestCase{
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
+ mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountMultiUser);
mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index d733d9d9a..b35f88edc 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -338,7 +338,8 @@ public class TransactionTests extends TelecomTestCase {
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
mClockProxy,
- mToastFactory);
+ mToastFactory,
+ mFeatureFlags);
Call callSpy = Mockito.spy(call);