diff options
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); |