diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto index b90067dae2a26..700baa1f4ee5d 100644 --- a/core/proto/android/companion/telecom.proto +++ b/core/proto/android/companion/telecom.proto @@ -42,6 +42,9 @@ message Telecom { ONGOING = 2; ON_HOLD = 3; RINGING_SILENCED = 4; + AUDIO_PROCESSING = 5; + RINGING_SIMULATED = 6; + DISCONNECTED = 7; } Status status = 3; @@ -89,8 +92,6 @@ message Telecom { END = 6; PUT_ON_HOLD = 7; TAKE_OFF_HOLD = 8; - REJECT_AND_BLOCK = 9; - IGNORE = 10; } // The list of active calls. diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java index 8570515f241d6..a2b71e0c38367 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java @@ -16,6 +16,7 @@ package com.android.server.companion; +import android.os.Binder; import android.provider.DeviceConfig; /** @@ -34,7 +35,12 @@ public class CompanionDeviceConfig { * Returns whether the given flag is currently enabled, with a default value of {@code false}. */ public static boolean isEnabled(String flag) { - return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false); + final long token = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false); + } finally { + Binder.restoreCallingIdentity(token); + } } /** diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ed61d645dc6e2..6b99494f65646 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -1383,10 +1383,11 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) { + public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback, + @CrossDeviceSyncControllerCallback.Type int type) { if (CompanionDeviceConfig.isEnabled( CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { - mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback); + mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback, type); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java index 3b108e63e13d8..c5ef4e49e5ea5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java @@ -36,7 +36,8 @@ public interface CompanionDeviceManagerServiceInternal { * Registers a callback from an InCallService / ConnectionService to CDM to process sync * requests and perform call control actions. */ - void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback); + void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback, + @CrossDeviceSyncControllerCallback.Type int type); /** * Requests a sync from an InCallService / ConnectionService to CDM, for the given association diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java index 459bf989321a1..7371824e9c9c7 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java @@ -17,6 +17,7 @@ package com.android.server.companion.datatransfer.contextsync; import android.media.AudioManager; +import android.net.Uri; import android.os.Bundle; import android.telecom.Call; import android.telecom.Connection; @@ -62,20 +63,14 @@ public class CallMetadataSyncConnectionService extends ConnectionService { final CallMetadataSyncConnection existingConnection = mActiveConnections.get(new CallMetadataSyncConnectionIdentifier( associationId, call.getId())); - if (existingConnection == null) { - final Bundle extras = new Bundle(); - extras.putInt(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID, - associationId); - extras.putParcelable(CrossDeviceSyncController.EXTRA_CALL, call); - mTelecomManager.addNewIncomingCall(call.getPhoneAccountHandle(), - extras); - } else { + if (existingConnection != null) { existingConnection.update(call); } } // Remove obsolete calls. mActiveConnections.values().removeIf(connection -> { - if (!callMetadataSyncData.hasCall(connection.getCallId())) { + if (associationId == connection.getAssociationId() + && !callMetadataSyncData.hasCall(connection.getCallId())) { connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE)); return true; } @@ -91,7 +86,8 @@ public class CallMetadataSyncConnectionService extends ConnectionService { mAudioManager = getSystemService(AudioManager.class); mTelecomManager = getSystemService(TelecomManager.class); mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class); - mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback); + mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback, + CrossDeviceSyncControllerCallback.TYPE_CONNECTION_SERVICE); } @Override @@ -101,6 +97,11 @@ public class CallMetadataSyncConnectionService extends ConnectionService { CrossDeviceSyncController.EXTRA_ASSOCIATION_ID); final CallMetadataSyncData.Call call = connectionRequest.getExtras().getParcelable( CrossDeviceSyncController.EXTRA_CALL, CallMetadataSyncData.Call.class); + // InCallServices outside of framework (like Dialer's) might try to read this, and crash + // when they can't. Remove it once we're done with it, as well as the other internal ones. + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL); + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID); + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID); final CallMetadataSyncConnection connection = new CallMetadataSyncConnection( mTelecomManager, mAudioManager, @@ -113,15 +114,17 @@ public class CallMetadataSyncConnectionService extends ConnectionService { CrossDeviceSyncController.createCallControlMessage(callId, action)); } }); - connection.setConnectionProperties( - Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED); + connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL); + connection.setInitializing(); return connection; } @Override public void onCreateIncomingConnectionFailed(PhoneAccountHandle phoneAccountHandle, ConnectionRequest connectionRequest) { - Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId()); + final String id = + phoneAccountHandle != null ? phoneAccountHandle.getId() : "unknown PhoneAccount"; + Slog.e(TAG, "onCreateOutgoingConnectionFailed for: " + id); } @Override @@ -132,7 +135,6 @@ public class CallMetadataSyncConnectionService extends ConnectionService { final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); call.setId(UUID.randomUUID().toString()); call.setStatus(android.companion.Telecom.Call.UNKNOWN_STATUS); - call.setPhoneAccountHandle(phoneAccountHandle); final CallMetadataSyncData.CallFacilitator callFacilitator = new CallMetadataSyncData.CallFacilitator(phoneAccount.getLabel().toString(), phoneAccount.getExtras().getString( @@ -142,6 +144,10 @@ public class CallMetadataSyncConnectionService extends ConnectionService { final int associationId = connectionRequest.getExtras().getInt( CrossDeviceSyncController.EXTRA_ASSOCIATION_ID); + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL); + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID); + connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID); + final CallMetadataSyncConnection connection = new CallMetadataSyncConnection( mTelecomManager, mAudioManager, @@ -154,8 +160,7 @@ public class CallMetadataSyncConnectionService extends ConnectionService { CrossDeviceSyncController.createCallControlMessage(callId, action)); } }); - connection.setConnectionProperties( - Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED); + connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL); mCdmsi.sendCrossDeviceSyncMessage(associationId, CrossDeviceSyncController.createCallCreateMessage(call.getId(), @@ -168,13 +173,21 @@ public class CallMetadataSyncConnectionService extends ConnectionService { @Override public void onCreateOutgoingConnectionFailed(PhoneAccountHandle phoneAccountHandle, ConnectionRequest connectionRequest) { - Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId()); + final String id = + phoneAccountHandle != null ? phoneAccountHandle.getId() : "unknown PhoneAccount"; + Slog.e(TAG, "onCreateOutgoingConnectionFailed for: " + id); } @Override public void onCreateConnectionComplete(Connection connection) { if (connection instanceof CallMetadataSyncConnection) { - ((CallMetadataSyncConnection) connection).initialize(); + final CallMetadataSyncConnection callMetadataSyncConnection = + (CallMetadataSyncConnection) connection; + callMetadataSyncConnection.initialize(); + mActiveConnections.put(new CallMetadataSyncConnectionIdentifier( + callMetadataSyncConnection.getAssociationId(), + callMetadataSyncConnection.getCallId()), + callMetadataSyncConnection); } } @@ -242,7 +255,11 @@ public class CallMetadataSyncConnectionService extends ConnectionService { return mCall.getId(); } - public void initialize() { + public int getAssociationId() { + return mAssociationId; + } + + private void initialize() { final int status = mCall.getStatus(); if (status == android.companion.Telecom.Call.RINGING_SILENCED) { mTelecomManager.silenceRinger(); @@ -254,12 +271,21 @@ public class CallMetadataSyncConnectionService extends ConnectionService { setActive(); } else if (state == Call.STATE_HOLDING) { setOnHold(); + } else if (state == Call.STATE_DISCONNECTED) { + setDisconnected(new DisconnectCause(DisconnectCause.REMOTE)); } else { - Slog.e(TAG, "Could not initialize call to unknown state"); + setInitialized(); + } + + final String callerId = mCall.getCallerId(); + if (callerId != null) { + setCallerDisplayName(callerId, TelecomManager.PRESENTATION_ALLOWED); + setAddress(Uri.fromParts("custom", mCall.getCallerId(), null), + TelecomManager.PRESENTATION_ALLOWED); } final Bundle extras = new Bundle(); - extras.putString(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId()); + extras.putString(CrossDeviceSyncController.EXTRA_CALL_ID, mCall.getId()); putExtras(extras); int capabilities = getConnectionCapabilities(); @@ -280,7 +306,7 @@ public class CallMetadataSyncConnectionService extends ConnectionService { } } - public void update(CallMetadataSyncData.Call call) { + private void update(CallMetadataSyncData.Call call) { final int status = call.getStatus(); if (status == android.companion.Telecom.Call.RINGING_SILENCED && mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) { @@ -295,31 +321,29 @@ public class CallMetadataSyncConnectionService extends ConnectionService { setActive(); } else if (state == Call.STATE_HOLDING) { setOnHold(); + } else if (state == Call.STATE_DISCONNECTED) { + setDisconnected(new DisconnectCause(DisconnectCause.REMOTE)); } else { Slog.e(TAG, "Could not update call to unknown state"); } } int capabilities = getConnectionCapabilities(); + mCall.setControls(call.getControls()); final boolean hasHoldControl = mCall.hasControl( android.companion.Telecom.PUT_ON_HOLD) || mCall.hasControl(android.companion.Telecom.TAKE_OFF_HOLD); - if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD) - == CAPABILITY_HOLD)) { - if (hasHoldControl) { - capabilities |= CAPABILITY_HOLD; - } else { - capabilities &= ~CAPABILITY_HOLD; - } + if (hasHoldControl) { + capabilities |= CAPABILITY_HOLD; + } else { + capabilities &= ~CAPABILITY_HOLD; } - final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE); - if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE) - == CAPABILITY_MUTE)) { - if (hasMuteControl) { - capabilities |= CAPABILITY_MUTE; - } else { - capabilities &= ~CAPABILITY_MUTE; - } + final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE) + || mCall.hasControl(android.companion.Telecom.UNMUTE); + if (hasMuteControl) { + capabilities |= CAPABILITY_MUTE; + } else { + capabilities &= ~CAPABILITY_MUTE; } mAudioManager.setMicrophoneMute( mCall.hasControl(android.companion.Telecom.UNMUTE)); diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java index b3cf772fc470b..d8621cb796abf 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.companion.ContextSyncMessage; import android.os.Parcel; import android.os.Parcelable; -import android.telecom.PhoneAccountHandle; import java.util.ArrayList; import java.util.Collection; @@ -189,7 +188,6 @@ class CallMetadataSyncData { private String mCallerId; private byte[] mAppIcon; private CallFacilitator mFacilitator; - private PhoneAccountHandle mPhoneAccountHandle; private int mStatus; private final Set mControls = new HashSet<>(); @@ -200,9 +198,6 @@ class CallMetadataSyncData { call.setAppIcon(parcel.readBlob()); call.setFacilitator(parcel.readParcelable(CallFacilitator.class.getClassLoader(), CallFacilitator.class)); - call.setPhoneAccountHandle( - parcel.readParcelable(PhoneAccountHandle.class.getClassLoader(), - android.telecom.PhoneAccountHandle.class)); call.setStatus(parcel.readInt()); final int numberOfControls = parcel.readInt(); for (int i = 0; i < numberOfControls; i++) { @@ -217,7 +212,6 @@ class CallMetadataSyncData { parcel.writeString(mCallerId); parcel.writeBlob(mAppIcon); parcel.writeParcelable(mFacilitator, parcelableFlags); - parcel.writeParcelable(mPhoneAccountHandle, parcelableFlags); parcel.writeInt(mStatus); parcel.writeInt(mControls.size()); for (int control : mControls) { @@ -241,10 +235,6 @@ class CallMetadataSyncData { mFacilitator = facilitator; } - void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { - mPhoneAccountHandle = phoneAccountHandle; - } - void setStatus(int status) { mStatus = status; } @@ -253,6 +243,11 @@ class CallMetadataSyncData { mControls.add(control); } + void setControls(Set controls) { + mControls.clear(); + mControls.addAll(controls); + } + String getId() { return mId; } @@ -269,10 +264,6 @@ class CallMetadataSyncData { return mFacilitator; } - PhoneAccountHandle getPhoneAccountHandle() { - return mPhoneAccountHandle; - } - int getStatus() { return mStatus; } diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java index 1f5e168b14e4a..b46d5d3eab54c 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java @@ -79,16 +79,15 @@ public class CallMetadataSyncInCallService extends InCallService { int callControlAction) { final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId, mCurrentCalls.values()); + if (crossDeviceCall == null) { + return; + } switch (callControlAction) { case android.companion.Telecom.ACCEPT: - if (crossDeviceCall != null) { - crossDeviceCall.doAccept(); - } + crossDeviceCall.doAccept(); break; case android.companion.Telecom.REJECT: - if (crossDeviceCall != null) { - crossDeviceCall.doReject(); - } + crossDeviceCall.doReject(); break; case android.companion.Telecom.SILENCE: doSilence(); @@ -100,19 +99,13 @@ public class CallMetadataSyncInCallService extends InCallService { doUnmute(); break; case android.companion.Telecom.END: - if (crossDeviceCall != null) { - crossDeviceCall.doEnd(); - } + crossDeviceCall.doEnd(); break; case android.companion.Telecom.PUT_ON_HOLD: - if (crossDeviceCall != null) { - crossDeviceCall.doPutOnHold(); - } + crossDeviceCall.doPutOnHold(); break; case android.companion.Telecom.TAKE_OFF_HOLD: - if (crossDeviceCall != null) { - crossDeviceCall.doTakeOffHold(); - } + crossDeviceCall.doTakeOffHold(); break; default: } @@ -148,7 +141,8 @@ public class CallMetadataSyncInCallService extends InCallService { super.onCreate(); if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class); - mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback); + mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback, + CrossDeviceSyncControllerCallback.TYPE_IN_CALL_SERVICE); } } @@ -156,7 +150,7 @@ public class CallMetadataSyncInCallService extends InCallService { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call, - call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState())))); + call -> new CrossDeviceCall(this, call, getCallAudioState())))); mCurrentCalls.keySet().forEach(call -> call.registerCallback(mTelecomCallback, getMainThreadHandler())); sync(getUserId()); @@ -182,7 +176,7 @@ public class CallMetadataSyncInCallService extends InCallService { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.put(call, - new CrossDeviceCall(getPackageManager(), call, getCallAudioState())); + new CrossDeviceCall(this, call, getCallAudioState())); call.registerCallback(mTelecomCallback); sync(getUserId()); } diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java index de7bf4022e046..fec6923e4d06e 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java @@ -17,10 +17,15 @@ package com.android.server.companion.datatransfer.contextsync; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.telecom.Call; import android.telecom.CallAudioState; +import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.util.Slog; @@ -35,40 +40,51 @@ public class CrossDeviceCall { private static final String TAG = "CrossDeviceCall"; - public static final String EXTRA_CALL_ID = - "com.android.companion.datatransfer.contextsync.extra.CALL_ID"; - private final String mId; - private Call mCall; + private final Call mCall; @VisibleForTesting boolean mIsEnterprise; - @VisibleForTesting boolean mIsOtt; private final String mCallingAppPackageName; private String mCallingAppName; private byte[] mCallingAppIcon; private String mCallerDisplayName; + private int mCallerDisplayNamePresentation; private int mStatus = android.companion.Telecom.Call.UNKNOWN_STATUS; private String mContactDisplayName; + private Uri mHandle; + private int mHandlePresentation; private boolean mIsMuted; private final Set mControls = new HashSet<>(); + private final boolean mIsCallPlacedByContextSync; - public CrossDeviceCall(PackageManager packageManager, @NonNull Call call, + public CrossDeviceCall(Context context, @NonNull Call call, CallAudioState callAudioState) { - this(packageManager, call.getDetails(), callAudioState); - mCall = call; - call.putExtra(EXTRA_CALL_ID, mId); + this(context, call, call.getDetails(), callAudioState); } - CrossDeviceCall(PackageManager packageManager, Call.Details callDetails, + CrossDeviceCall(Context context, Call.Details callDetails, CallAudioState callAudioState) { + this(context, /* call= */ null, callDetails, callAudioState); + } + + private CrossDeviceCall(Context context, @Nullable Call call, + Call.Details callDetails, CallAudioState callAudioState) { + mCall = call; final String predefinedId = callDetails.getIntentExtras() != null - ? callDetails.getIntentExtras().getString(EXTRA_CALL_ID) : null; - mId = predefinedId != null ? predefinedId : UUID.randomUUID().toString(); + ? callDetails.getIntentExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID) + : null; + final String generatedId = UUID.randomUUID().toString(); + mId = predefinedId != null ? (generatedId + predefinedId) : generatedId; + if (call != null) { + call.putExtra(CrossDeviceSyncController.EXTRA_CALL_ID, mId); + } + mIsCallPlacedByContextSync = + new ComponentName(context, CallMetadataSyncConnectionService.class) + .equals(callDetails.getAccountHandle().getComponentName()); mCallingAppPackageName = callDetails.getAccountHandle().getComponentName().getPackageName(); - mIsOtt = (callDetails.getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED) - == Call.Details.PROPERTY_SELF_MANAGED; mIsEnterprise = (callDetails.getCallProperties() & Call.Details.PROPERTY_ENTERPRISE_CALL) == Call.Details.PROPERTY_ENTERPRISE_CALL; + final PackageManager packageManager = context.getPackageManager(); try { final ApplicationInfo applicationInfo = packageManager .getApplicationInfo(mCallingAppPackageName, @@ -108,7 +124,10 @@ public class CrossDeviceCall { @VisibleForTesting void updateCallDetails(Call.Details callDetails) { mCallerDisplayName = callDetails.getCallerDisplayName(); + mCallerDisplayNamePresentation = callDetails.getCallerDisplayNamePresentation(); mContactDisplayName = callDetails.getContactDisplayName(); + mHandle = callDetails.getHandle(); + mHandlePresentation = callDetails.getHandlePresentation(); mStatus = convertStateToStatus(callDetails.getState()); mControls.clear(); if (mStatus == android.companion.Telecom.Call.RINGING @@ -145,7 +164,14 @@ public class CrossDeviceCall { return android.companion.Telecom.Call.ONGOING; case Call.STATE_RINGING: return android.companion.Telecom.Call.RINGING; + case Call.STATE_AUDIO_PROCESSING: + return android.companion.Telecom.Call.AUDIO_PROCESSING; + case Call.STATE_SIMULATED_RINGING: + return android.companion.Telecom.Call.RINGING_SIMULATED; + case Call.STATE_DISCONNECTED: + return android.companion.Telecom.Call.DISCONNECTED; default: + Slog.e(TAG, "Couldn't resolve state to status: " + callState); return android.companion.Telecom.Call.UNKNOWN_STATUS; } } @@ -163,6 +189,12 @@ public class CrossDeviceCall { case android.companion.Telecom.Call.RINGING: case android.companion.Telecom.Call.RINGING_SILENCED: return Call.STATE_RINGING; + case android.companion.Telecom.Call.AUDIO_PROCESSING: + return Call.STATE_AUDIO_PROCESSING; + case android.companion.Telecom.Call.RINGING_SIMULATED: + return Call.STATE_SIMULATED_RINGING; + case android.companion.Telecom.Call.DISCONNECTED: + return Call.STATE_DISCONNECTED; case android.companion.Telecom.Call.UNKNOWN_STATUS: default: return Call.STATE_NEW; @@ -195,10 +227,23 @@ public class CrossDeviceCall { * @param isAdminBlocked whether there is an admin that has blocked contacts over Bluetooth */ public String getReadableCallerId(boolean isAdminBlocked) { - if (mIsOtt) { + if (mIsEnterprise && isAdminBlocked) { + // Cannot use any contact information. + return getNonContactString(); + } + return mContactDisplayName != null ? mContactDisplayName : getNonContactString(); + } + + private String getNonContactString() { + if (mCallerDisplayName != null + && mCallerDisplayNamePresentation == TelecomManager.PRESENTATION_ALLOWED) { return mCallerDisplayName; } - return mIsEnterprise && isAdminBlocked ? mCallerDisplayName : mContactDisplayName; + if (mHandle != null && mHandle.getSchemeSpecificPart() != null + && mHandlePresentation == TelecomManager.PRESENTATION_ALLOWED) { + return mHandle.getSchemeSpecificPart(); + } + return null; } public int getStatus() { @@ -209,6 +254,10 @@ public class CrossDeviceCall { return mControls; } + public boolean isCallPlacedByContextSync() { + return mIsCallPlacedByContextSync; + } + void doAccept() { mCall.answer(VideoProfile.STATE_AUDIO_ONLY); } diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java index 8c6ff86d6d63b..bf82f3f4a352d 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java @@ -20,6 +20,7 @@ import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_C import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; +import android.companion.CompanionDeviceManager; import android.companion.ContextSyncMessage; import android.companion.IOnMessageReceivedListener; import android.companion.IOnTransportsChangedListener; @@ -44,8 +45,10 @@ import com.android.server.companion.CompanionDeviceConfig; import com.android.server.companion.transport.CompanionTransportManager; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -54,6 +57,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; /** * Monitors connections and sending / receiving of synced data. @@ -62,6 +66,13 @@ public class CrossDeviceSyncController { private static final String TAG = "CrossDeviceSyncController"; + public static final String EXTRA_CALL_ID = + "com.android.companion.datatransfer.contextsync.extra.CALL_ID"; + static final String EXTRA_FACILITATOR_ICON = + "com.android.companion.datatransfer.contextsync.extra.FACILITATOR_ICON"; + static final String EXTRA_IS_REMOTE_ORIGIN = + "com.android.companion.datatransfer.contextsync.extra.IS_REMOTE_ORIGIN"; + static final String EXTRA_ASSOCIATION_ID = "com.android.server.companion.datatransfer.contextsync.extra.ASSOCIATION_ID"; static final String EXTRA_CALL = @@ -78,11 +89,13 @@ public class CrossDeviceSyncController { private final Context mContext; private final CompanionTransportManager mCompanionTransportManager; private final PhoneAccountManager mPhoneAccountManager; + private final CallManager mCallManager; private final List mConnectedAssociations = new ArrayList<>(); private final Set mBlocklist = new HashSet<>(); private final List mCallFacilitators = new ArrayList<>(); - private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback; + private WeakReference mInCallServiceCallbackRef; + private WeakReference mConnectionServiceCallbackRef; public CrossDeviceSyncController(Context context, CompanionTransportManager companionTransportManager) { @@ -104,25 +117,77 @@ public class CrossDeviceSyncController { mConnectedAssociations); mConnectedAssociations.clear(); mConnectedAssociations.addAll(newAssociations); - if (mCrossDeviceSyncControllerCallback == null) { - Slog.w(TAG, "No callback to report transports changed"); - return; - } for (AssociationInfo associationInfo : newAssociations) { - if (!existingAssociations.contains(associationInfo) - && !isAssociationBlocked(associationInfo.getId())) { - mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( - associationInfo.getUserId(), /* added= */ true); - mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); + if (!existingAssociations.contains(associationInfo)) { + // New association. + if (!isAssociationBlocked(associationInfo)) { + final CrossDeviceSyncControllerCallback callback = + mInCallServiceCallbackRef != null + ? mInCallServiceCallbackRef.get() : null; + if (callback != null) { + callback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ true); + callback.requestCrossDeviceSync(associationInfo); + } else { + Slog.w(TAG, "No callback to report new transport"); + syncMessageToDevice(associationInfo.getId(), + createFacilitatorMessage()); + } + } else { + mBlocklist.add(associationInfo.getId()); + Slog.i(TAG, "New association was blocked from context syncing"); + } } } for (AssociationInfo associationInfo : existingAssociations) { if (!newAssociations.contains(associationInfo)) { - if (isAssociationBlocked(associationInfo.getId())) { - mBlocklist.remove(associationInfo.getId()); - } else { - mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( - associationInfo.getUserId(), /* added= */ false); + // Removed association! + mBlocklist.remove(associationInfo.getId()); + if (!isAssociationBlockedLocal(associationInfo.getId())) { + final CrossDeviceSyncControllerCallback callback = + mInCallServiceCallbackRef != null + ? mInCallServiceCallbackRef.get() : null; + if (callback != null) { + callback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ false); + } else { + Slog.w(TAG, "No callback to report removed transport"); + } + } + } else { + // Stable association! + final boolean systemBlocked = isAssociationBlocked(associationInfo); + if (isAssociationBlockedLocal(associationInfo.getId()) != systemBlocked) { + // Block state has changed. + final CrossDeviceSyncControllerCallback callback = + mInCallServiceCallbackRef != null + ? mInCallServiceCallbackRef.get() : null; + if (!systemBlocked) { + Slog.i(TAG, "Unblocking existing association for context sync"); + mBlocklist.remove(associationInfo.getId()); + if (callback != null) { + callback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ true); + callback.requestCrossDeviceSync(associationInfo); + } else { + Slog.w(TAG, "No callback to report changed transport"); + syncMessageToDevice(associationInfo.getId(), + createFacilitatorMessage()); + } + } else { + Slog.i(TAG, "Blocking existing association for context sync"); + mBlocklist.add(associationInfo.getId()); + if (callback != null) { + callback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ false); + } else { + Slog.w(TAG, "No callback to report changed transport"); + } + // Send empty message to device to clear its data (otherwise it + // will get stale) + syncMessageToDevice(associationInfo.getId(), + createEmptyMessage()); + } } } } @@ -132,18 +197,48 @@ public class CrossDeviceSyncController { new IOnMessageReceivedListener.Stub() { @Override public void onMessageReceived(int associationId, byte[] data) { + if (isAssociationBlockedLocal(associationId)) { + return; + } final CallMetadataSyncData processedData = processTelecomDataFromSync(data); mPhoneAccountManager.updateFacilitators(associationId, processedData); - processCallCreateRequests(associationId, processedData); - if (mCrossDeviceSyncControllerCallback == null) { + mCallManager.updateCalls(associationId, processedData); + processCallCreateRequests(processedData); + if (mInCallServiceCallbackRef == null + && mConnectionServiceCallbackRef == null) { Slog.w(TAG, "No callback to process context sync message"); return; } - mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId, - processedData); + final CrossDeviceSyncControllerCallback inCallServiceCallback = + mInCallServiceCallbackRef != null ? mInCallServiceCallbackRef.get() + : null; + if (inCallServiceCallback != null) { + inCallServiceCallback.processContextSyncMessage(associationId, + processedData); + } else { + // This is dead; get rid of it lazily + mInCallServiceCallbackRef = null; + } + + final CrossDeviceSyncControllerCallback connectionServiceCallback = + mConnectionServiceCallbackRef != null + ? mConnectionServiceCallbackRef.get() : null; + if (connectionServiceCallback != null) { + connectionServiceCallback.processContextSyncMessage(associationId, + processedData); + } else { + // This is dead; get rid of it lazily + mConnectionServiceCallbackRef = null; + } } }); mPhoneAccountManager = new PhoneAccountManager(mContext); + mCallManager = new CallManager(mContext, mPhoneAccountManager); + } + + private static boolean isAssociationBlocked(AssociationInfo info) { + return (info.getSystemDataSyncFlags() & CompanionDeviceManager.FLAG_CALL_METADATA) + != CompanionDeviceManager.FLAG_CALL_METADATA; } /** Invoke set-up tasks that happen when boot is completed. */ @@ -155,7 +250,7 @@ public class CrossDeviceSyncController { mPhoneAccountManager.onBootCompleted(); final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); - if (telecomManager.getCallCapablePhoneAccounts().size() != 0) { + if (telecomManager != null && telecomManager.getCallCapablePhoneAccounts().size() != 0) { final PhoneAccountHandle defaultOutgoingTelAccountHandle = telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL); if (defaultOutgoingTelAccountHandle != null) { @@ -171,8 +266,7 @@ public class CrossDeviceSyncController { } } - private void processCallCreateRequests(int associationId, - CallMetadataSyncData callMetadataSyncData) { + private void processCallCreateRequests(CallMetadataSyncData callMetadataSyncData) { final Iterator iterator = callMetadataSyncData.getCallCreateRequests().iterator(); while (iterator.hasNext()) { @@ -184,7 +278,7 @@ public class CrossDeviceSyncController { final Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, request.getAddress().replaceAll("\\D+", ""), /* fragment= */ null); final Bundle extras = new Bundle(); - extras.putString(CrossDeviceCall.EXTRA_CALL_ID, request.getId()); + extras.putString(EXTRA_CALL_ID, request.getId()); final Bundle outerExtras = new Bundle(); outerExtras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); mContext.getSystemService(TelecomManager.class).placeCall(uri, outerExtras); @@ -196,39 +290,33 @@ public class CrossDeviceSyncController { } } - private boolean isAssociationBlocked(int associationId) { + /** + * This keeps track of "previous" state to calculate deltas. Use {@link #isAssociationBlocked} + * for all other use cases. + */ + private boolean isAssociationBlockedLocal(int associationId) { return mBlocklist.contains(associationId); } /** Registers the call metadata callback. */ - public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) { - mCrossDeviceSyncControllerCallback = callback; - for (AssociationInfo associationInfo : mConnectedAssociations) { - if (!isAssociationBlocked(associationInfo.getId())) { - mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( - associationInfo.getUserId(), /* added= */ true); - mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); - } - } - } - - /** Allow specific associated devices to enable / disable syncing. */ - public void setSyncEnabled(AssociationInfo associationInfo, boolean enabled) { - if (enabled) { - if (isAssociationBlocked(associationInfo.getId())) { - mBlocklist.remove(associationInfo.getId()); - mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( - associationInfo.getUserId(), /* added= */ true); - mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); + public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback, + @CrossDeviceSyncControllerCallback.Type int type) { + if (type == CrossDeviceSyncControllerCallback.TYPE_IN_CALL_SERVICE) { + mInCallServiceCallbackRef = new WeakReference<>(callback); + for (AssociationInfo associationInfo : mConnectedAssociations) { + if (!isAssociationBlocked(associationInfo)) { + mBlocklist.remove(associationInfo.getId()); + callback.updateNumberOfActiveSyncAssociations(associationInfo.getUserId(), + /* added= */ true); + callback.requestCrossDeviceSync(associationInfo); + } else { + mBlocklist.add(associationInfo.getId()); + } } + } else if (type == CrossDeviceSyncControllerCallback.TYPE_CONNECTION_SERVICE) { + mConnectionServiceCallbackRef = new WeakReference<>(callback); } else { - if (!isAssociationBlocked(associationInfo.getId())) { - mBlocklist.add(associationInfo.getId()); - mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( - associationInfo.getUserId(), /* added= */ false); - // Send empty message to device to clear its data (otherwise it will get stale) - syncMessageToDevice(associationInfo.getId(), createEmptyMessage()); - } + Slog.e(TAG, "Cannot register callback of unknown type: " + type); } } @@ -246,8 +334,7 @@ public class CrossDeviceSyncController { public void syncToAllDevicesForUserId(int userId, Collection calls) { final Set associationIds = new HashSet<>(); for (AssociationInfo associationInfo : mConnectedAssociations) { - if (associationInfo.getUserId() == userId && !isAssociationBlocked( - associationInfo.getId())) { + if (associationInfo.getUserId() == userId && !isAssociationBlocked(associationInfo)) { associationIds.add(associationInfo.getId()); } } @@ -269,7 +356,7 @@ public class CrossDeviceSyncController { */ public void syncToSingleDevice(AssociationInfo associationInfo, Collection calls) { - if (isAssociationBlocked(associationInfo.getId())) { + if (isAssociationBlocked(associationInfo)) { Slog.e(TAG, "Cannot sync to requested device; connection is blocked"); return; } @@ -286,7 +373,7 @@ public class CrossDeviceSyncController { * @param message The message to sync. */ public void syncMessageToDevice(int associationId, byte[] message) { - if (isAssociationBlocked(associationId)) { + if (isAssociationBlockedLocal(associationId)) { Slog.e(TAG, "Cannot sync to requested device; connection is blocked"); return; } @@ -491,6 +578,10 @@ public class CrossDeviceSyncController { pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION); final long telecomToken = pos.start(ContextSyncMessage.TELECOM); for (CrossDeviceCall call : calls) { + if (call.isCallPlacedByContextSync()) { + // Do not sync any calls which our "ours" as that would be duplicative. + continue; + } final long callsToken = pos.start(Telecom.CALLS); pos.write(Telecom.Call.ID, call.getId()); final long originToken = pos.start(Telecom.Call.ORIGIN); @@ -559,6 +650,50 @@ public class CrossDeviceSyncController { return pos.getBytes(); } + /** Create a facilitator-only message, used before any calls are available as a call intake. */ + private byte[] createFacilitatorMessage() { + return createCallUpdateMessage(Collections.emptyList(), -1); + } + + @VisibleForTesting + static class CallManager { + + @VisibleForTesting final Map> mCallIds = new HashMap<>(); + private final TelecomManager mTelecomManager; + private final PhoneAccountManager mPhoneAccountManager; + + CallManager(Context context, PhoneAccountManager phoneAccountManager) { + mTelecomManager = context.getSystemService(TelecomManager.class); + mPhoneAccountManager = phoneAccountManager; + } + + /** Add any new calls to Telecom. The ConnectionService will handle everything else. */ + void updateCalls(int associationId, CallMetadataSyncData data) { + final Set oldCallIds = mCallIds.getOrDefault(associationId, new HashSet<>()); + final Set newCallIds = data.getCalls().stream().map( + CallMetadataSyncData.Call::getId).collect(Collectors.toSet()); + if (oldCallIds.equals(newCallIds)) { + return; + } + + for (CallMetadataSyncData.Call currentCall : data.getCalls()) { + if (!oldCallIds.contains(currentCall.getId()) + && currentCall.getFacilitator() != null) { + final Bundle extras = new Bundle(); + extras.putInt(EXTRA_ASSOCIATION_ID, associationId); + extras.putBoolean(EXTRA_IS_REMOTE_ORIGIN, true); + extras.putParcelable(EXTRA_CALL, currentCall); + extras.putString(EXTRA_CALL_ID, currentCall.getId()); + extras.putByteArray(EXTRA_FACILITATOR_ICON, currentCall.getAppIcon()); + final PhoneAccountHandle handle = mPhoneAccountManager.getPhoneAccountHandle( + associationId, currentCall.getFacilitator().getIdentifier()); + mTelecomManager.addNewIncomingCall(handle, extras); + } + } + mCallIds.put(associationId, newCallIds); + } + } + static class PhoneAccountManager { private final Map mPhoneAccountHandles = new HashMap<>(); diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java index 31e10a8145681..8a0ba27b75262 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java @@ -16,11 +16,25 @@ package com.android.server.companion.datatransfer.contextsync; +import android.annotation.IntDef; import android.companion.AssociationInfo; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** Callback for call metadata syncing. */ public abstract class CrossDeviceSyncControllerCallback { + static final int TYPE_CONNECTION_SERVICE = 1; + static final int TYPE_IN_CALL_SERVICE = 2; + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_CONNECTION_SERVICE, + TYPE_IN_CALL_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type { + } + void processContextSyncMessage(int associationId, CallMetadataSyncData callMetadataSyncData) {} void requestCrossDeviceSync(AssociationInfo associationInfo) {} diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java index ccddb2fd95bc7..1475537dee14c 100644 --- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java @@ -75,7 +75,7 @@ public class CallMetadataSyncConnectionServiceTest { callMetadataSyncData.addCall(call); mSyncConnectionService.mCrossDeviceSyncControllerCallback.processContextSyncMessage( /* associationId= */ 0, callMetadataSyncData); - verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any()); + verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java index 5a0646c0e0e51..6a939ab347688 100644 --- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java @@ -23,6 +23,7 @@ import android.platform.test.annotations.Presubmit; import android.telecom.Call; import android.telecom.ParcelableCall; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.testing.AndroidTestingRunner; import androidx.test.InstrumentationRegistry; @@ -45,7 +46,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_uninitialized() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); assertWithMessage("Wrong status").that(crossDeviceCall.getStatus()) .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS); @@ -55,7 +56,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_ringing() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -70,7 +71,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_ongoing() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -85,7 +86,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_holding() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_HOLDING, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -99,7 +100,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_cannotHold() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_MUTE)); @@ -113,7 +114,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_cannotMute() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_HOLD)); @@ -127,7 +128,7 @@ public class CrossDeviceCallTest { @Test public void updateCallDetails_transitionRingingToOngoing() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -150,7 +151,7 @@ public class CrossDeviceCallTest { @Test public void updateSilencedIfRinging_ringing_silenced() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -165,7 +166,7 @@ public class CrossDeviceCallTest { @Test public void updateSilencedIfRinging_notRinging_notSilenced() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE)); @@ -179,12 +180,11 @@ public class CrossDeviceCallTest { } @Test - public void getReadableCallerId_enterpriseCall_adminBlocked_ott() { + public void getReadableCallerId_enterpriseCall_adminBlocked_hasContact() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.mIsEnterprise = true; - crossDeviceCall.mIsOtt = true; crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); @@ -195,76 +195,74 @@ public class CrossDeviceCallTest { } @Test - public void getReadableCallerId_enterpriseCall_adminUnblocked_ott() { + public void getReadableCallerId_enterpriseCall_adminUnblocked_hasContact() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.mIsEnterprise = true; - crossDeviceCall.mIsOtt = true; crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); final String result = crossDeviceCall.getReadableCallerId(false); + assertWithMessage("Wrong caller id").that(result) + .isEqualTo(CONTACT_DISPLAY_NAME); + } + + @Test + public void getReadableCallerId_enterpriseCall_adminBlocked_noContact() { + final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( + InstrumentationRegistry.getTargetContext(), + mUninitializedCallDetails, /* callAudioState= */ null); + crossDeviceCall.mIsEnterprise = true; + crossDeviceCall.updateCallDetails( + createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */ + false)); + + final String result = crossDeviceCall.getReadableCallerId(true); + + assertWithMessage("Wrong caller id").that(result) + .isEqualTo(CALLER_DISPLAY_NAME); + } + + @Test + public void getReadableCallerId_nonEnterpriseCall_adminBlocked_noContact() { + final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( + InstrumentationRegistry.getTargetContext(), + mUninitializedCallDetails, /* callAudioState= */ null); + crossDeviceCall.mIsEnterprise = false; + crossDeviceCall.updateCallDetails( + createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */ + false)); + + final String result = crossDeviceCall.getReadableCallerId(true); + + assertWithMessage("Wrong caller id").that(result) + .isEqualTo(CALLER_DISPLAY_NAME); + } + + @Test + public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_noContact() { + final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( + InstrumentationRegistry.getTargetContext(), + mUninitializedCallDetails, /* callAudioState= */ null); + crossDeviceCall.mIsEnterprise = false; + crossDeviceCall.updateCallDetails( + createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */ + false)); + + final String result = crossDeviceCall.getReadableCallerId(false); + assertWithMessage("Wrong caller id").that(result) .isEqualTo(CALLER_DISPLAY_NAME); } @Test - public void getReadableCallerId_enterpriseCall_adminBlocked_pstn() { + public void getReadableCallerId_nonEnterpriseCall_adminBlocked_hasContact() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), - mUninitializedCallDetails, /* callAudioState= */ null); - crossDeviceCall.mIsEnterprise = true; - crossDeviceCall.mIsOtt = false; - crossDeviceCall.updateCallDetails( - createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); - - final String result = crossDeviceCall.getReadableCallerId(true); - - assertWithMessage("Wrong caller id").that(result) - .isEqualTo(CALLER_DISPLAY_NAME); - } - - @Test - public void getReadableCallerId_nonEnterpriseCall_adminBlocked_ott() { - final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.mIsEnterprise = false; - crossDeviceCall.mIsOtt = true; - crossDeviceCall.updateCallDetails( - createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); - - final String result = crossDeviceCall.getReadableCallerId(true); - - assertWithMessage("Wrong caller id").that(result) - .isEqualTo(CALLER_DISPLAY_NAME); - } - - @Test - public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_ott() { - final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), - mUninitializedCallDetails, /* callAudioState= */ null); - crossDeviceCall.mIsEnterprise = false; - crossDeviceCall.mIsOtt = true; - crossDeviceCall.updateCallDetails( - createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); - - final String result = crossDeviceCall.getReadableCallerId(false); - - assertWithMessage("Wrong caller id").that(result) - .isEqualTo(CALLER_DISPLAY_NAME); - } - - @Test - public void getReadableCallerId_nonEnterpriseCall_adminBlocked_pstn() { - final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), - mUninitializedCallDetails, /* callAudioState= */ null); - crossDeviceCall.mIsEnterprise = false; - crossDeviceCall.mIsOtt = false; crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); @@ -275,12 +273,11 @@ public class CrossDeviceCallTest { } @Test - public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_pstn() { + public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_hasContact() { final CrossDeviceCall crossDeviceCall = new CrossDeviceCall( - InstrumentationRegistry.getTargetContext().getPackageManager(), + InstrumentationRegistry.getTargetContext(), mUninitializedCallDetails, /* callAudioState= */ null); crossDeviceCall.mIsEnterprise = false; - crossDeviceCall.mIsOtt = false; crossDeviceCall.updateCallDetails( createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0)); @@ -291,10 +288,17 @@ public class CrossDeviceCallTest { } private Call.Details createCallDetails(int state, int capabilities) { + return createCallDetails(state, capabilities, /* hasContactName= */ true); + } + + private Call.Details createCallDetails(int state, int capabilities, boolean hasContactName) { final ParcelableCall.ParcelableCallBuilder parcelableCallBuilder = new ParcelableCall.ParcelableCallBuilder(); parcelableCallBuilder.setCallerDisplayName(CALLER_DISPLAY_NAME); - parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME); + if (hasContactName) { + parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME); + } + parcelableCallBuilder.setCallerDisplayNamePresentation(TelecomManager.PRESENTATION_ALLOWED); parcelableCallBuilder.setCapabilities(capabilities); parcelableCallBuilder.setState(state); parcelableCallBuilder.setConferenceableCallIds(Collections.emptyList()); diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java index 33e7cd2891fc6..7688fcb2faba5 100644 --- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -211,4 +212,62 @@ public class CrossDeviceSyncControllerTest { verify(mMockTelecomManager, times(1)).registerPhoneAccount(any()); verify(mMockTelecomManager, times(1)).unregisterPhoneAccount(any()); } + + @Test + public void updateCalls_newCall() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + call.setId("123abc"); + call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test")); + final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData(); + callMetadataSyncData.addCall(call); + final CrossDeviceSyncController.CallManager callManager = + new CrossDeviceSyncController.CallManager(mMockContext, + new CrossDeviceSyncController.PhoneAccountManager(mMockContext)); + callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData); + verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any()); + } + + @Test + public void updateCalls_newCall_noFacilitator() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + call.setId("123abc"); + final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData(); + callMetadataSyncData.addCall(call); + final CrossDeviceSyncController.CallManager callManager = + new CrossDeviceSyncController.CallManager(mMockContext, + new CrossDeviceSyncController.PhoneAccountManager(mMockContext)); + callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData); + verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any()); + } + + @Test + public void updateCalls_existingCall() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + call.setId("123abc"); + final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData(); + callMetadataSyncData.addCall(call); + final CrossDeviceSyncController.CallManager callManager = + new CrossDeviceSyncController.CallManager(mMockContext, + new CrossDeviceSyncController.PhoneAccountManager(mMockContext)); + callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId())); + callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData); + verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any()); + } + + @Test + public void updateCalls_removedCall() { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + call.setId("123abc"); + final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData(); + callMetadataSyncData.addCall(call); + final CrossDeviceSyncController.CallManager callManager = + new CrossDeviceSyncController.CallManager(mMockContext, + new CrossDeviceSyncController.PhoneAccountManager(mMockContext)); + callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId(), "fakeCallId")); + callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData); + verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any()); + assertWithMessage("Hasn't removed the id of the removed call") + .that(callManager.mCallIds) + .containsExactly(/* associationId= */ 0, Set.of(call.getId())); + } }