Merge changes I02fb643b,If62d66bc into udc-dev

* changes:
  Sync facilitators on transport ready
  Small context sync telecom bugfixes
This commit is contained in:
Christine Franks
2023-05-13 03:49:42 +00:00
committed by Android (Google) Code Review
13 changed files with 495 additions and 216 deletions

View File

@@ -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.

View File

@@ -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);
}
}
/**

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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<Integer> 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<Integer> 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;
}

View File

@@ -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());
}

View File

@@ -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<Integer> 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);
}

View File

@@ -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<AssociationInfo> mConnectedAssociations = new ArrayList<>();
private final Set<Integer> mBlocklist = new HashSet<>();
private final List<CallMetadataSyncData.CallFacilitator> mCallFacilitators = new ArrayList<>();
private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback;
private WeakReference<CrossDeviceSyncControllerCallback> mInCallServiceCallbackRef;
private WeakReference<CrossDeviceSyncControllerCallback> 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<CallMetadataSyncData.CallCreateRequest> 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<CrossDeviceCall> calls) {
final Set<Integer> 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<CrossDeviceCall> 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<Integer, Set<String>> 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<String> oldCallIds = mCallIds.getOrDefault(associationId, new HashSet<>());
final Set<String> 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<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
new HashMap<>();

View File

@@ -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) {}

View File

@@ -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

View File

@@ -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());

View File

@@ -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()));
}
}