Merge "Split and streamline create and control requests" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b6751e8275
@@ -31,11 +31,8 @@ message Telecom {
|
||||
// Caller's name and/or phone number; what a user would see displayed when receiving an
|
||||
// incoming call on the local device
|
||||
string caller_id = 1;
|
||||
// Human-readable name of the app processing this call
|
||||
string app_name = 2;
|
||||
bytes app_icon = 3;
|
||||
// Unique identifier for this app, such as a package name.
|
||||
string app_identifier = 4;
|
||||
bytes app_icon = 2;
|
||||
CallFacilitator facilitator = 3;
|
||||
}
|
||||
Origin origin = 2;
|
||||
|
||||
@@ -51,6 +48,37 @@ message Telecom {
|
||||
repeated Control controls = 4;
|
||||
}
|
||||
|
||||
message Request {
|
||||
message CreateAction {
|
||||
// UUID representing this request.
|
||||
string id = 1;
|
||||
// URI representing the address of the intended callee.
|
||||
string address = 2;
|
||||
// Which facilitator should handle this call.
|
||||
CallFacilitator facilitator = 3;
|
||||
}
|
||||
message ControlAction {
|
||||
// UUID representing the call to perform the control action on
|
||||
string id = 1;
|
||||
// The control to perform
|
||||
Control control = 2;
|
||||
}
|
||||
|
||||
oneof action {
|
||||
CreateAction create_action = 1;
|
||||
ControlAction control_action = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// A facilitator (namely an app) that can be directed to place calls.
|
||||
// Next index: 3
|
||||
message CallFacilitator {
|
||||
// Human-readable name of the facilitator
|
||||
string name = 1;
|
||||
// Unique identifier for this facilitator, such as a package name.
|
||||
string identifier = 2;
|
||||
}
|
||||
|
||||
enum Control {
|
||||
UNKNOWN_CONTROL = 0;
|
||||
ACCEPT = 1;
|
||||
@@ -68,5 +96,7 @@ message Telecom {
|
||||
// The list of active calls.
|
||||
repeated Call calls = 1;
|
||||
// The list of requested calls or call changes.
|
||||
repeated Call requests = 2;
|
||||
repeated Request requests = 2;
|
||||
// The list of call facilitators that this device currently supports.
|
||||
repeated CallFacilitator facilitators = 3;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,21 @@
|
||||
|
||||
package com.android.server.companion.datatransfer.contextsync;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.telecom.Call;
|
||||
import android.telecom.Connection;
|
||||
import android.telecom.ConnectionRequest;
|
||||
import android.telecom.ConnectionService;
|
||||
import android.telecom.DisconnectCause;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.companion.CompanionDeviceManagerServiceInternal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -39,10 +42,47 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
|
||||
|
||||
private static final String TAG = "CallMetadataSyncConnectionService";
|
||||
|
||||
private AudioManager mAudioManager;
|
||||
private TelecomManager mTelecomManager;
|
||||
private final Map<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
|
||||
new HashMap<>();
|
||||
@VisibleForTesting
|
||||
AudioManager mAudioManager;
|
||||
@VisibleForTesting
|
||||
TelecomManager mTelecomManager;
|
||||
private CompanionDeviceManagerServiceInternal mCdmsi;
|
||||
@VisibleForTesting
|
||||
final Map<CallMetadataSyncConnectionIdentifier, CallMetadataSyncConnection>
|
||||
mActiveConnections = new HashMap<>();
|
||||
@VisibleForTesting
|
||||
final CrossDeviceSyncControllerCallback
|
||||
mCrossDeviceSyncControllerCallback = new CrossDeviceSyncControllerCallback() {
|
||||
|
||||
@Override
|
||||
void processContextSyncMessage(int associationId,
|
||||
CallMetadataSyncData callMetadataSyncData) {
|
||||
// Add new calls or update existing calls.
|
||||
for (CallMetadataSyncData.Call call : callMetadataSyncData.getCalls()) {
|
||||
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 {
|
||||
existingConnection.update(call);
|
||||
}
|
||||
}
|
||||
// Remove obsolete calls.
|
||||
mActiveConnections.values().removeIf(connection -> {
|
||||
if (!callMetadataSyncData.hasCall(connection.getCallId())) {
|
||||
connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@@ -50,83 +90,96 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
|
||||
|
||||
mAudioManager = getSystemService(AudioManager.class);
|
||||
mTelecomManager = getSystemService(TelecomManager.class);
|
||||
mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class);
|
||||
mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
|
||||
* device.
|
||||
*/
|
||||
private void registerPhoneAccount(int associationId, String appIdentifier,
|
||||
String humanReadableAppName) {
|
||||
final PhoneAccountHandleIdentifier phoneAccountHandleIdentifier =
|
||||
new PhoneAccountHandleIdentifier(associationId, appIdentifier);
|
||||
final PhoneAccount phoneAccount = createPhoneAccount(phoneAccountHandleIdentifier,
|
||||
humanReadableAppName);
|
||||
mTelecomManager.registerPhoneAccount(phoneAccount);
|
||||
mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(phoneAccountHandleIdentifier),
|
||||
true);
|
||||
@Override
|
||||
public Connection onCreateIncomingConnection(PhoneAccountHandle phoneAccountHandle,
|
||||
ConnectionRequest connectionRequest) {
|
||||
final int associationId = connectionRequest.getExtras().getInt(
|
||||
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
|
||||
final CallMetadataSyncData.Call call = connectionRequest.getExtras().getParcelable(
|
||||
CrossDeviceSyncController.EXTRA_CALL, CallMetadataSyncData.Call.class);
|
||||
final CallMetadataSyncConnection connection = new CallMetadataSyncConnection(
|
||||
mTelecomManager,
|
||||
mAudioManager,
|
||||
associationId,
|
||||
call,
|
||||
new CallMetadataSyncConnectionCallback() {
|
||||
@Override
|
||||
void sendCallAction(int associationId, String callId, int action) {
|
||||
mCdmsi.sendCrossDeviceSyncMessage(associationId,
|
||||
CrossDeviceSyncController.createCallControlMessage(callId, action));
|
||||
}
|
||||
});
|
||||
connection.setConnectionProperties(
|
||||
Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED);
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
|
||||
* device.
|
||||
*/
|
||||
private void unregisterPhoneAccount(int associationId, String appIdentifier) {
|
||||
mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(
|
||||
new PhoneAccountHandleIdentifier(associationId, appIdentifier)));
|
||||
@Override
|
||||
public void onCreateIncomingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
|
||||
ConnectionRequest connectionRequest) {
|
||||
Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection onCreateOutgoingConnection(PhoneAccountHandle phoneAccountHandle,
|
||||
ConnectionRequest connectionRequest) {
|
||||
final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
|
||||
|
||||
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(
|
||||
CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID));
|
||||
call.setFacilitator(callFacilitator);
|
||||
|
||||
final int associationId = connectionRequest.getExtras().getInt(
|
||||
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
|
||||
|
||||
final CallMetadataSyncConnection connection = new CallMetadataSyncConnection(
|
||||
mTelecomManager,
|
||||
mAudioManager,
|
||||
associationId,
|
||||
call,
|
||||
new CallMetadataSyncConnectionCallback() {
|
||||
@Override
|
||||
void sendCallAction(int associationId, String callId, int action) {
|
||||
mCdmsi.sendCrossDeviceSyncMessage(associationId,
|
||||
CrossDeviceSyncController.createCallControlMessage(callId, action));
|
||||
}
|
||||
});
|
||||
connection.setConnectionProperties(
|
||||
Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED);
|
||||
|
||||
mCdmsi.sendCrossDeviceSyncMessage(associationId,
|
||||
CrossDeviceSyncController.createCallCreateMessage(call.getId(),
|
||||
connectionRequest.getAddress().toString(),
|
||||
call.getFacilitator().getIdentifier()));
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOutgoingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
|
||||
ConnectionRequest connectionRequest) {
|
||||
Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateConnectionComplete(Connection connection) {
|
||||
if (connection instanceof CallMetadataSyncConnection) {
|
||||
((CallMetadataSyncConnection) connection).initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PhoneAccount createPhoneAccount(PhoneAccountHandleIdentifier phoneAccountHandleIdentifier,
|
||||
String humanReadableAppName) {
|
||||
if (mPhoneAccountHandles.containsKey(phoneAccountHandleIdentifier)) {
|
||||
// Already exists!
|
||||
return null;
|
||||
}
|
||||
final PhoneAccountHandle handle = new PhoneAccountHandle(
|
||||
new ComponentName(this, CallMetadataSyncConnectionService.class),
|
||||
UUID.randomUUID().toString());
|
||||
mPhoneAccountHandles.put(phoneAccountHandleIdentifier, handle);
|
||||
return new PhoneAccount.Builder(handle, humanReadableAppName)
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
|
||||
| PhoneAccount.CAPABILITY_SELF_MANAGED).build();
|
||||
}
|
||||
|
||||
static final class PhoneAccountHandleIdentifier {
|
||||
private final int mAssociationId;
|
||||
private final String mAppIdentifier;
|
||||
|
||||
PhoneAccountHandleIdentifier(int associationId, String appIdentifier) {
|
||||
mAssociationId = associationId;
|
||||
mAppIdentifier = appIdentifier;
|
||||
}
|
||||
|
||||
public int getAssociationId() {
|
||||
return mAssociationId;
|
||||
}
|
||||
|
||||
public String getAppIdentifier() {
|
||||
return mAppIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mAssociationId, mAppIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof PhoneAccountHandleIdentifier) {
|
||||
return ((PhoneAccountHandleIdentifier) other).getAssociationId() == mAssociationId
|
||||
&& mAppIdentifier != null
|
||||
&& mAppIdentifier.equals(
|
||||
((PhoneAccountHandleIdentifier) other).getAppIdentifier());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CallMetadataSyncConnectionIdentifier {
|
||||
static final class CallMetadataSyncConnectionIdentifier {
|
||||
private final int mAssociationId;
|
||||
private final String mCallId;
|
||||
|
||||
@@ -153,18 +206,21 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
|
||||
if (other instanceof CallMetadataSyncConnectionIdentifier) {
|
||||
return ((CallMetadataSyncConnectionIdentifier) other).getAssociationId()
|
||||
== mAssociationId
|
||||
&& (((CallMetadataSyncConnectionIdentifier) other).getCallId() == mCallId);
|
||||
&& mCallId != null && mCallId.equals(
|
||||
((CallMetadataSyncConnectionIdentifier) other).getCallId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class CallMetadataSyncConnectionCallback {
|
||||
@VisibleForTesting
|
||||
abstract static class CallMetadataSyncConnectionCallback {
|
||||
|
||||
abstract void sendCallAction(int associationId, String callId, int action);
|
||||
}
|
||||
|
||||
private static class CallMetadataSyncConnection extends Connection {
|
||||
@VisibleForTesting
|
||||
static class CallMetadataSyncConnection extends Connection {
|
||||
|
||||
private final TelecomManager mTelecomManager;
|
||||
private final AudioManager mAudioManager;
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -34,7 +35,9 @@ import java.util.Set;
|
||||
class CallMetadataSyncData {
|
||||
|
||||
final Map<String, CallMetadataSyncData.Call> mCalls = new HashMap<>();
|
||||
final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>();
|
||||
final List<CallMetadataSyncData.CallCreateRequest> mCallCreateRequests = new ArrayList<>();
|
||||
final List<CallMetadataSyncData.CallControlRequest> mCallControlRequests = new ArrayList<>();
|
||||
final List<CallMetadataSyncData.CallFacilitator> mCallFacilitators = new ArrayList<>();
|
||||
|
||||
public void addCall(CallMetadataSyncData.Call call) {
|
||||
mCalls.put(call.getId(), call);
|
||||
@@ -48,20 +51,145 @@ class CallMetadataSyncData {
|
||||
return mCalls.values();
|
||||
}
|
||||
|
||||
public void addRequest(CallMetadataSyncData.Call call) {
|
||||
mRequests.add(call);
|
||||
public void addCallCreateRequest(CallMetadataSyncData.CallCreateRequest request) {
|
||||
mCallCreateRequests.add(request);
|
||||
}
|
||||
|
||||
public List<CallMetadataSyncData.Call> getRequests() {
|
||||
return mRequests;
|
||||
public List<CallMetadataSyncData.CallCreateRequest> getCallCreateRequests() {
|
||||
return mCallCreateRequests;
|
||||
}
|
||||
|
||||
public void addCallControlRequest(CallMetadataSyncData.CallControlRequest request) {
|
||||
mCallControlRequests.add(request);
|
||||
}
|
||||
|
||||
public List<CallMetadataSyncData.CallControlRequest> getCallControlRequests() {
|
||||
return mCallControlRequests;
|
||||
}
|
||||
|
||||
public void addFacilitator(CallFacilitator facilitator) {
|
||||
mCallFacilitators.add(facilitator);
|
||||
}
|
||||
|
||||
public List<CallFacilitator> getFacilitators() {
|
||||
return mCallFacilitators;
|
||||
}
|
||||
|
||||
public static class CallFacilitator implements Parcelable {
|
||||
private String mName;
|
||||
private String mIdentifier;
|
||||
|
||||
CallFacilitator() {}
|
||||
|
||||
CallFacilitator(String name, String identifier) {
|
||||
mName = name;
|
||||
mIdentifier = identifier;
|
||||
}
|
||||
|
||||
CallFacilitator(Parcel parcel) {
|
||||
this(parcel.readString(), parcel.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int parcelableFlags) {
|
||||
parcel.writeString(mName);
|
||||
parcel.writeString(mIdentifier);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return mIdentifier;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public void setIdentifier(String identifier) {
|
||||
mIdentifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<CallFacilitator> CREATOR =
|
||||
new Parcelable.Creator<>() {
|
||||
|
||||
@Override
|
||||
public CallFacilitator createFromParcel(Parcel source) {
|
||||
return new CallFacilitator(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallFacilitator[] newArray(int size) {
|
||||
return new CallFacilitator[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class CallControlRequest {
|
||||
private String mId;
|
||||
private int mControl;
|
||||
|
||||
public void setId(String id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public void setControl(int control) {
|
||||
mControl = control;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public int getControl() {
|
||||
return mControl;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CallCreateRequest {
|
||||
private String mId;
|
||||
private String mAddress;
|
||||
private CallFacilitator mFacilitator;
|
||||
|
||||
public void setId(String id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
mAddress = address;
|
||||
}
|
||||
|
||||
public void setFacilitator(CallFacilitator facilitator) {
|
||||
mFacilitator = facilitator;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return mAddress;
|
||||
}
|
||||
|
||||
public CallFacilitator getFacilitator() {
|
||||
return mFacilitator;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Call implements Parcelable {
|
||||
private String mId;
|
||||
private String mCallerId;
|
||||
private byte[] mAppIcon;
|
||||
private String mAppName;
|
||||
private String mAppIdentifier;
|
||||
private CallFacilitator mFacilitator;
|
||||
private PhoneAccountHandle mPhoneAccountHandle;
|
||||
private int mStatus;
|
||||
private final Set<Integer> mControls = new HashSet<>();
|
||||
|
||||
@@ -70,8 +198,11 @@ class CallMetadataSyncData {
|
||||
call.setId(parcel.readString());
|
||||
call.setCallerId(parcel.readString());
|
||||
call.setAppIcon(parcel.readBlob());
|
||||
call.setAppName(parcel.readString());
|
||||
call.setAppIdentifier(parcel.readString());
|
||||
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++) {
|
||||
@@ -85,8 +216,8 @@ class CallMetadataSyncData {
|
||||
parcel.writeString(mId);
|
||||
parcel.writeString(mCallerId);
|
||||
parcel.writeBlob(mAppIcon);
|
||||
parcel.writeString(mAppName);
|
||||
parcel.writeString(mAppIdentifier);
|
||||
parcel.writeParcelable(mFacilitator, parcelableFlags);
|
||||
parcel.writeParcelable(mPhoneAccountHandle, parcelableFlags);
|
||||
parcel.writeInt(mStatus);
|
||||
parcel.writeInt(mControls.size());
|
||||
for (int control : mControls) {
|
||||
@@ -106,12 +237,12 @@ class CallMetadataSyncData {
|
||||
mAppIcon = appIcon;
|
||||
}
|
||||
|
||||
void setAppName(String appName) {
|
||||
mAppName = appName;
|
||||
void setFacilitator(CallFacilitator facilitator) {
|
||||
mFacilitator = facilitator;
|
||||
}
|
||||
|
||||
void setAppIdentifier(String appIdentifier) {
|
||||
mAppIdentifier = appIdentifier;
|
||||
void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
|
||||
mPhoneAccountHandle = phoneAccountHandle;
|
||||
}
|
||||
|
||||
void setStatus(int status) {
|
||||
@@ -134,12 +265,12 @@ class CallMetadataSyncData {
|
||||
return mAppIcon;
|
||||
}
|
||||
|
||||
String getAppName() {
|
||||
return mAppName;
|
||||
CallFacilitator getFacilitator() {
|
||||
return mFacilitator;
|
||||
}
|
||||
|
||||
String getAppIdentifier() {
|
||||
return mAppIdentifier;
|
||||
PhoneAccountHandle getPhoneAccountHandle() {
|
||||
return mPhoneAccountHandle;
|
||||
}
|
||||
|
||||
int getStatus() {
|
||||
|
||||
@@ -66,16 +66,11 @@ public class CallMetadataSyncInCallService extends InCallService {
|
||||
@Override
|
||||
void processContextSyncMessage(int associationId,
|
||||
CallMetadataSyncData callMetadataSyncData) {
|
||||
final Iterator<CallMetadataSyncData.Call> iterator =
|
||||
callMetadataSyncData.getRequests().iterator();
|
||||
final Iterator<CallMetadataSyncData.CallControlRequest> iterator =
|
||||
callMetadataSyncData.getCallControlRequests().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final CallMetadataSyncData.Call call = iterator.next();
|
||||
if (call.getId() != null) {
|
||||
// The call is already assigned an id; treat as control invocations.
|
||||
for (int control : call.getControls()) {
|
||||
processCallControlAction(call.getId(), control);
|
||||
}
|
||||
}
|
||||
final CallMetadataSyncData.CallControlRequest request = iterator.next();
|
||||
processCallControlAction(request.getId(), request.getControl());
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,15 @@ import android.companion.ContextSyncMessage;
|
||||
import android.companion.IOnMessageReceivedListener;
|
||||
import android.companion.IOnTransportsChangedListener;
|
||||
import android.companion.Telecom;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.util.Slog;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
@@ -40,9 +46,14 @@ import com.android.server.companion.transport.CompanionTransportManager;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Monitors connections and sending / receiving of synced data.
|
||||
@@ -51,13 +62,25 @@ public class CrossDeviceSyncController {
|
||||
|
||||
private static final String TAG = "CrossDeviceSyncController";
|
||||
|
||||
static final String EXTRA_ASSOCIATION_ID =
|
||||
"com.android.server.companion.datatransfer.contextsync.extra.ASSOCIATION_ID";
|
||||
static final String EXTRA_CALL =
|
||||
"com.android.server.companion.datatransfer.contextsync.extra.CALL";
|
||||
static final String EXTRA_CALL_FACILITATOR_ID =
|
||||
"com.android.server.companion.datatransfer.contextsync.extra.CALL_FACILITATOR_ID";
|
||||
// Special facilitator id corresponding to TelecomManager#placeCall usage (with address of
|
||||
// schema tel:). All other facilitators use Intent#actionCall.
|
||||
public static final String FACILITATOR_ID_SYSTEM = "system";
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
private static final int CURRENT_VERSION = VERSION_1;
|
||||
|
||||
private final Context mContext;
|
||||
private final CompanionTransportManager mCompanionTransportManager;
|
||||
private final PhoneAccountManager mPhoneAccountManager;
|
||||
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;
|
||||
|
||||
@@ -81,7 +104,6 @@ public class CrossDeviceSyncController {
|
||||
mConnectedAssociations);
|
||||
mConnectedAssociations.clear();
|
||||
mConnectedAssociations.addAll(newAssociations);
|
||||
|
||||
if (mCrossDeviceSyncControllerCallback == null) {
|
||||
Slog.w(TAG, "No callback to report transports changed");
|
||||
return;
|
||||
@@ -110,14 +132,43 @@ public class CrossDeviceSyncController {
|
||||
new IOnMessageReceivedListener.Stub() {
|
||||
@Override
|
||||
public void onMessageReceived(int associationId, byte[] data) {
|
||||
final CallMetadataSyncData processedData = processTelecomDataFromSync(data);
|
||||
mPhoneAccountManager.updateFacilitators(associationId, processedData);
|
||||
processCallCreateRequests(associationId, processedData);
|
||||
if (mCrossDeviceSyncControllerCallback == null) {
|
||||
Slog.w(TAG, "No callback to process context sync message");
|
||||
return;
|
||||
}
|
||||
mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId,
|
||||
processTelecomDataFromSync(data));
|
||||
processedData);
|
||||
}
|
||||
});
|
||||
mPhoneAccountManager = new PhoneAccountManager(mContext);
|
||||
}
|
||||
|
||||
private void processCallCreateRequests(int associationId,
|
||||
CallMetadataSyncData callMetadataSyncData) {
|
||||
final Iterator<CallMetadataSyncData.CallCreateRequest> iterator =
|
||||
callMetadataSyncData.getCallCreateRequests().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final CallMetadataSyncData.CallCreateRequest request = iterator.next();
|
||||
if (FACILITATOR_ID_SYSTEM.equals(request.getFacilitator().getIdentifier())) {
|
||||
if (request.getAddress() != null && request.getAddress().startsWith(
|
||||
PhoneAccount.SCHEME_TEL)) {
|
||||
// Remove all the non-numbers (dashes, parens, scheme)
|
||||
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());
|
||||
final Bundle outerExtras = new Bundle();
|
||||
outerExtras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
|
||||
mContext.getSystemService(TelecomManager.class).placeCall(uri, outerExtras);
|
||||
}
|
||||
} else {
|
||||
Slog.e(TAG, "Non-system facilitated calls are not supported yet");
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAssociationBlocked(int associationId) {
|
||||
@@ -241,8 +292,35 @@ public class CrossDeviceSyncController {
|
||||
pis.end(callsToken);
|
||||
} else if (pis.getFieldNumber() == (int) Telecom.REQUESTS) {
|
||||
final long requestsToken = pis.start(Telecom.REQUESTS);
|
||||
callMetadataSyncData.addRequest(processCallDataFromSync(pis));
|
||||
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (pis.getFieldNumber()) {
|
||||
case (int) Telecom.Request.CREATE_ACTION:
|
||||
final long createActionToken = pis.start(
|
||||
Telecom.Request.CREATE_ACTION);
|
||||
callMetadataSyncData.addCallCreateRequest(
|
||||
processCallCreateRequestDataFromSync(pis));
|
||||
pis.end(createActionToken);
|
||||
break;
|
||||
case (int) Telecom.Request.CONTROL_ACTION:
|
||||
final long controlActionToken = pis.start(
|
||||
Telecom.Request.CONTROL_ACTION);
|
||||
callMetadataSyncData.addCallControlRequest(
|
||||
processCallControlRequestDataFromSync(pis));
|
||||
pis.end(controlActionToken);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG,
|
||||
"Unhandled field in Request:"
|
||||
+ ProtoUtils.currentFieldToString(
|
||||
pis));
|
||||
}
|
||||
}
|
||||
pis.end(requestsToken);
|
||||
} else if (pis.getFieldNumber() == (int) Telecom.FACILITATORS) {
|
||||
final long facilitatorsToken = pis.start(Telecom.FACILITATORS);
|
||||
callMetadataSyncData.addFacilitator(
|
||||
processFacilitatorDataFromSync(pis));
|
||||
pis.end(facilitatorsToken);
|
||||
} else {
|
||||
Slog.e(TAG, "Unhandled field in Telecom:"
|
||||
+ ProtoUtils.currentFieldToString(pis));
|
||||
@@ -264,6 +342,79 @@ public class CrossDeviceSyncController {
|
||||
return callMetadataSyncData;
|
||||
}
|
||||
|
||||
/** Process an incoming message with a call create request. */
|
||||
public static CallMetadataSyncData.CallCreateRequest processCallCreateRequestDataFromSync(
|
||||
ProtoInputStream pis) throws IOException {
|
||||
CallMetadataSyncData.CallCreateRequest callCreateRequest =
|
||||
new CallMetadataSyncData.CallCreateRequest();
|
||||
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (pis.getFieldNumber()) {
|
||||
case (int) Telecom.Request.CreateAction.ID:
|
||||
callCreateRequest.setId(pis.readString(Telecom.Request.CreateAction.ID));
|
||||
break;
|
||||
case (int) Telecom.Request.CreateAction.ADDRESS:
|
||||
callCreateRequest.setAddress(
|
||||
pis.readString(Telecom.Request.CreateAction.ADDRESS));
|
||||
break;
|
||||
case (int) Telecom.Request.CreateAction.FACILITATOR:
|
||||
final long facilitatorToken = pis.start(
|
||||
Telecom.Request.CreateAction.FACILITATOR);
|
||||
callCreateRequest.setFacilitator(processFacilitatorDataFromSync(pis));
|
||||
pis.end(facilitatorToken);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG,
|
||||
"Unhandled field in CreateAction:" + ProtoUtils.currentFieldToString(
|
||||
pis));
|
||||
}
|
||||
}
|
||||
return callCreateRequest;
|
||||
}
|
||||
|
||||
/** Process an incoming message with a call control request. */
|
||||
public static CallMetadataSyncData.CallControlRequest processCallControlRequestDataFromSync(
|
||||
ProtoInputStream pis) throws IOException {
|
||||
final CallMetadataSyncData.CallControlRequest callControlRequest =
|
||||
new CallMetadataSyncData.CallControlRequest();
|
||||
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (pis.getFieldNumber()) {
|
||||
case (int) Telecom.Request.ControlAction.ID:
|
||||
callControlRequest.setId(pis.readString(Telecom.Request.ControlAction.ID));
|
||||
break;
|
||||
case (int) Telecom.Request.ControlAction.CONTROL:
|
||||
callControlRequest.setControl(
|
||||
pis.readInt(Telecom.Request.ControlAction.CONTROL));
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG,
|
||||
"Unhandled field in ControlAction:" + ProtoUtils.currentFieldToString(
|
||||
pis));
|
||||
}
|
||||
}
|
||||
return callControlRequest;
|
||||
}
|
||||
|
||||
/** Process an incoming message with facilitators. */
|
||||
public static CallMetadataSyncData.CallFacilitator processFacilitatorDataFromSync(
|
||||
ProtoInputStream pis) throws IOException {
|
||||
final CallMetadataSyncData.CallFacilitator facilitator =
|
||||
new CallMetadataSyncData.CallFacilitator();
|
||||
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (pis.getFieldNumber()) {
|
||||
case (int) Telecom.CallFacilitator.NAME:
|
||||
facilitator.setName(pis.readString(Telecom.CallFacilitator.NAME));
|
||||
break;
|
||||
case (int) Telecom.CallFacilitator.IDENTIFIER:
|
||||
facilitator.setIdentifier(pis.readString(Telecom.CallFacilitator.IDENTIFIER));
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Unhandled field in Facilitator:"
|
||||
+ ProtoUtils.currentFieldToString(pis));
|
||||
}
|
||||
}
|
||||
return facilitator;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CallMetadataSyncData.Call processCallDataFromSync(ProtoInputStream pis) throws IOException {
|
||||
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
|
||||
@@ -279,15 +430,14 @@ public class CrossDeviceSyncController {
|
||||
case (int) Telecom.Call.Origin.APP_ICON:
|
||||
call.setAppIcon(pis.readBytes(Telecom.Call.Origin.APP_ICON));
|
||||
break;
|
||||
case (int) Telecom.Call.Origin.APP_NAME:
|
||||
call.setAppName(pis.readString(Telecom.Call.Origin.APP_NAME));
|
||||
break;
|
||||
case (int) Telecom.Call.Origin.CALLER_ID:
|
||||
call.setCallerId(pis.readString(Telecom.Call.Origin.CALLER_ID));
|
||||
break;
|
||||
case (int) Telecom.Call.Origin.APP_IDENTIFIER:
|
||||
call.setAppIdentifier(
|
||||
pis.readString(Telecom.Call.Origin.APP_IDENTIFIER));
|
||||
case (int) Telecom.Call.Origin.FACILITATOR:
|
||||
final long facilitatorToken = pis.start(
|
||||
Telecom.Call.Origin.FACILITATOR);
|
||||
call.setFacilitator(processFacilitatorDataFromSync(pis));
|
||||
pis.end(facilitatorToken);
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Unhandled field in Origin:"
|
||||
@@ -322,8 +472,10 @@ public class CrossDeviceSyncController {
|
||||
pos.write(Telecom.Call.Origin.CALLER_ID,
|
||||
call.getReadableCallerId(isAdminBlocked(userId)));
|
||||
pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon());
|
||||
pos.write(Telecom.Call.Origin.APP_NAME, call.getCallingAppName());
|
||||
pos.write(Telecom.Call.Origin.APP_IDENTIFIER, call.getCallingAppPackageName());
|
||||
final long facilitatorToken = pos.start(Telecom.Call.Origin.FACILITATOR);
|
||||
pos.write(Telecom.CallFacilitator.NAME, call.getCallingAppName());
|
||||
pos.write(Telecom.CallFacilitator.IDENTIFIER, call.getCallingAppPackageName());
|
||||
pos.end(facilitatorToken);
|
||||
pos.end(originToken);
|
||||
pos.write(Telecom.Call.STATUS, call.getStatus());
|
||||
for (int control : call.getControls()) {
|
||||
@@ -331,6 +483,12 @@ public class CrossDeviceSyncController {
|
||||
}
|
||||
pos.end(callsToken);
|
||||
}
|
||||
for (CallMetadataSyncData.CallFacilitator facilitator : mCallFacilitators) {
|
||||
final long facilitatorsToken = pos.start(Telecom.FACILITATORS);
|
||||
pos.write(Telecom.CallFacilitator.NAME, facilitator.getName());
|
||||
pos.write(Telecom.CallFacilitator.IDENTIFIER, facilitator.getIdentifier());
|
||||
pos.end(facilitatorsToken);
|
||||
}
|
||||
pos.end(telecomToken);
|
||||
return pos.getBytes();
|
||||
}
|
||||
@@ -341,8 +499,29 @@ public class CrossDeviceSyncController {
|
||||
pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
|
||||
final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
|
||||
final long requestsToken = pos.start(Telecom.REQUESTS);
|
||||
pos.write(Telecom.Call.ID, callId);
|
||||
pos.write(Telecom.Call.CONTROLS, control);
|
||||
final long actionToken = pos.start(Telecom.Request.CONTROL_ACTION);
|
||||
pos.write(Telecom.Request.ControlAction.ID, callId);
|
||||
pos.write(Telecom.Request.ControlAction.CONTROL, control);
|
||||
pos.end(actionToken);
|
||||
pos.end(requestsToken);
|
||||
pos.end(telecomToken);
|
||||
return pos.getBytes();
|
||||
}
|
||||
|
||||
/** Create a call creation message (used to place a call). */
|
||||
public static byte[] createCallCreateMessage(String id, String callAddress,
|
||||
String facilitatorIdentifier) {
|
||||
final ProtoOutputStream pos = new ProtoOutputStream();
|
||||
pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
|
||||
final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
|
||||
final long requestsToken = pos.start(Telecom.REQUESTS);
|
||||
final long actionToken = pos.start(Telecom.Request.CREATE_ACTION);
|
||||
pos.write(Telecom.Request.CreateAction.ID, id);
|
||||
pos.write(Telecom.Request.CreateAction.ADDRESS, callAddress);
|
||||
final long facilitatorToken = pos.start(Telecom.Request.CreateAction.FACILITATOR);
|
||||
pos.write(Telecom.CallFacilitator.IDENTIFIER, facilitatorIdentifier);
|
||||
pos.end(facilitatorToken);
|
||||
pos.end(actionToken);
|
||||
pos.end(requestsToken);
|
||||
pos.end(telecomToken);
|
||||
return pos.getBytes();
|
||||
@@ -354,4 +533,131 @@ public class CrossDeviceSyncController {
|
||||
pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
|
||||
return pos.getBytes();
|
||||
}
|
||||
|
||||
static class PhoneAccountManager {
|
||||
private final Map<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
|
||||
new HashMap<>();
|
||||
private final TelecomManager mTelecomManager;
|
||||
private final ComponentName mConnectionServiceComponentName;
|
||||
|
||||
PhoneAccountManager(Context context) {
|
||||
mTelecomManager = context.getSystemService(TelecomManager.class);
|
||||
mConnectionServiceComponentName = new ComponentName(context,
|
||||
CallMetadataSyncConnectionService.class);
|
||||
}
|
||||
|
||||
PhoneAccountHandle getPhoneAccountHandle(int associationId, String appIdentifier) {
|
||||
return mPhoneAccountHandles.get(
|
||||
new PhoneAccountHandleIdentifier(associationId, appIdentifier));
|
||||
}
|
||||
|
||||
void updateFacilitators(int associationId, CallMetadataSyncData data) {
|
||||
final ArrayList<CallMetadataSyncData.CallFacilitator> facilitators = new ArrayList<>();
|
||||
for (CallMetadataSyncData.Call call : data.getCalls()) {
|
||||
facilitators.add(call.getFacilitator());
|
||||
}
|
||||
facilitators.addAll(data.getFacilitators());
|
||||
updateFacilitators(associationId, facilitators);
|
||||
}
|
||||
|
||||
private void updateFacilitators(int associationId,
|
||||
List<CallMetadataSyncData.CallFacilitator> facilitators) {
|
||||
final Iterator<PhoneAccountHandleIdentifier> iterator =
|
||||
mPhoneAccountHandles.keySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final PhoneAccountHandleIdentifier handleIdentifier = iterator.next();
|
||||
final String handleAppIdentifier = handleIdentifier.getAppIdentifier();
|
||||
final int handleAssociationId = handleIdentifier.getAssociationId();
|
||||
if (associationId == handleAssociationId && facilitators.stream().noneMatch(
|
||||
facilitator -> handleAppIdentifier != null && handleAppIdentifier.equals(
|
||||
facilitator.getIdentifier()))) {
|
||||
unregisterPhoneAccount(mPhoneAccountHandles.get(handleIdentifier));
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for (CallMetadataSyncData.CallFacilitator facilitator : facilitators) {
|
||||
final PhoneAccountHandleIdentifier phoneAccountHandleIdentifier =
|
||||
new PhoneAccountHandleIdentifier(associationId,
|
||||
facilitator.getIdentifier());
|
||||
if (!mPhoneAccountHandles.containsKey(phoneAccountHandleIdentifier)) {
|
||||
registerPhoneAccount(phoneAccountHandleIdentifier, facilitator.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link android.telecom.PhoneAccount} for a given call-capable app on the
|
||||
* synced device, and records it in the local {@link #mPhoneAccountHandles} map.
|
||||
*/
|
||||
private void registerPhoneAccount(PhoneAccountHandleIdentifier handleIdentifier,
|
||||
String humanReadableAppName) {
|
||||
if (mPhoneAccountHandles.containsKey(handleIdentifier)) {
|
||||
// Already exists!
|
||||
return;
|
||||
}
|
||||
final PhoneAccountHandle handle = new PhoneAccountHandle(
|
||||
mConnectionServiceComponentName,
|
||||
UUID.randomUUID().toString());
|
||||
mPhoneAccountHandles.put(handleIdentifier, handle);
|
||||
final PhoneAccount phoneAccount = createPhoneAccount(handle, humanReadableAppName,
|
||||
handleIdentifier.getAppIdentifier());
|
||||
mTelecomManager.registerPhoneAccount(phoneAccount);
|
||||
mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(handleIdentifier), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a {@link android.telecom.PhoneAccount} for a given call-capable app on the
|
||||
* synced device. Does NOT remove it from the {@link #mPhoneAccountHandles} map.
|
||||
*/
|
||||
private void unregisterPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
|
||||
mTelecomManager.unregisterPhoneAccount(phoneAccountHandle);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static PhoneAccount createPhoneAccount(PhoneAccountHandle handle,
|
||||
String humanReadableAppName,
|
||||
String appIdentifier) {
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(EXTRA_CALL_FACILITATOR_ID, appIdentifier);
|
||||
return new PhoneAccount.Builder(handle, humanReadableAppName)
|
||||
.setExtras(extras)
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
|
||||
| PhoneAccount.CAPABILITY_CONNECTION_MANAGER).build();
|
||||
}
|
||||
}
|
||||
|
||||
static final class PhoneAccountHandleIdentifier {
|
||||
private final int mAssociationId;
|
||||
private final String mAppIdentifier;
|
||||
|
||||
PhoneAccountHandleIdentifier(int associationId, String appIdentifier) {
|
||||
mAssociationId = associationId;
|
||||
mAppIdentifier = appIdentifier;
|
||||
}
|
||||
|
||||
public int getAssociationId() {
|
||||
return mAssociationId;
|
||||
}
|
||||
|
||||
public String getAppIdentifier() {
|
||||
return mAppIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mAssociationId, mAppIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof PhoneAccountHandleIdentifier) {
|
||||
return ((PhoneAccountHandleIdentifier) other).getAssociationId() == mAssociationId
|
||||
&& mAppIdentifier != null
|
||||
&& mAppIdentifier.equals(
|
||||
((PhoneAccountHandleIdentifier) other).getAppIdentifier());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,52 +16,85 @@
|
||||
|
||||
package com.android.server.companion.datatransfer.contextsync;
|
||||
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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 android.media.AudioManager;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class CallMetadataSyncConnectionServiceTest {
|
||||
|
||||
private CallMetadataSyncConnectionService mSyncConnectionService;
|
||||
@Mock
|
||||
private TelecomManager mMockTelecomManager;
|
||||
@Mock
|
||||
private AudioManager mMockAudioManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doNothing().when(mMockTelecomManager).registerPhoneAccount(any());
|
||||
doNothing().when(mMockTelecomManager).unregisterPhoneAccount(any());
|
||||
mSyncConnectionService = new CallMetadataSyncConnectionService() {
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return "android";
|
||||
}
|
||||
};
|
||||
mSyncConnectionService.mTelecomManager = mMockTelecomManager;
|
||||
mSyncConnectionService.mAudioManager = mMockAudioManager;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPhoneAccount_success() {
|
||||
final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
|
||||
new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
|
||||
associationId= */
|
||||
0, "com.google.test"), "Test App");
|
||||
assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
|
||||
public void processContextSyncMessage_empty() {
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
mSyncConnectionService.mCrossDeviceSyncControllerCallback.processContextSyncMessage(
|
||||
/* associationId= */ 0, callMetadataSyncData);
|
||||
verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPhoneAccount_alreadyExists_doesNotCreateAnother() {
|
||||
final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
|
||||
new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
|
||||
associationId= */
|
||||
0, "com.google.test"), "Test App");
|
||||
final PhoneAccount phoneAccount2 = mSyncConnectionService.createPhoneAccount(
|
||||
new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
|
||||
associationId= */
|
||||
0, "com.google.test"), "Test App #2");
|
||||
assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
|
||||
assertWithMessage("Unexpectedly created second phone account").that(phoneAccount2).isNull();
|
||||
public void processContextSyncMessage_newCall() {
|
||||
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
|
||||
call.setId("123abc");
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
callMetadataSyncData.addCall(call);
|
||||
mSyncConnectionService.mCrossDeviceSyncControllerCallback.processContextSyncMessage(
|
||||
/* associationId= */ 0, callMetadataSyncData);
|
||||
verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processContextSyncMessage_existingCall() {
|
||||
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
|
||||
call.setId("123abc");
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
callMetadataSyncData.addCall(call);
|
||||
mSyncConnectionService.mActiveConnections.put(
|
||||
new CallMetadataSyncConnectionService.CallMetadataSyncConnectionIdentifier(
|
||||
/* asscociationId= */ 0, "123abc"),
|
||||
new CallMetadataSyncConnectionService.CallMetadataSyncConnection(
|
||||
mMockTelecomManager, mMockAudioManager, 0, call,
|
||||
new CallMetadataSyncConnectionService.CallMetadataSyncConnectionCallback() {
|
||||
@Override
|
||||
void sendCallAction(int associationId, String callId, int action) {}
|
||||
}));
|
||||
mSyncConnectionService.mCrossDeviceSyncControllerCallback.processContextSyncMessage(
|
||||
/* associationId= */ 0, callMetadataSyncData);
|
||||
verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ public class CallMetadataSyncDataTest {
|
||||
call.setId(id);
|
||||
call.setCallerId(callerId);
|
||||
call.setAppIcon(appIcon);
|
||||
call.setAppName(appName);
|
||||
call.setAppIdentifier(appIdentifier);
|
||||
final CallMetadataSyncData.CallFacilitator callFacilitator =
|
||||
new CallMetadataSyncData.CallFacilitator(appName, appIdentifier);
|
||||
call.setFacilitator(callFacilitator);
|
||||
call.setStatus(status);
|
||||
call.addControl(control1);
|
||||
call.addControl(control2);
|
||||
@@ -56,8 +57,8 @@ public class CallMetadataSyncDataTest {
|
||||
assertThat(reconstructedCall.getId()).isEqualTo(id);
|
||||
assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId);
|
||||
assertThat(reconstructedCall.getAppIcon()).isEqualTo(appIcon);
|
||||
assertThat(reconstructedCall.getAppName()).isEqualTo(appName);
|
||||
assertThat(reconstructedCall.getAppIdentifier()).isEqualTo(appIdentifier);
|
||||
assertThat(reconstructedCall.getFacilitator().getName()).isEqualTo(appName);
|
||||
assertThat(reconstructedCall.getFacilitator().getIdentifier()).isEqualTo(appIdentifier);
|
||||
assertThat(reconstructedCall.getStatus()).isEqualTo(status);
|
||||
assertThat(reconstructedCall.getControls()).containsExactly(control1, control2);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,19 @@ package com.android.server.companion.datatransfer.contextsync;
|
||||
|
||||
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.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
@@ -47,6 +56,10 @@ public class CrossDeviceSyncControllerTest {
|
||||
private CompanionTransportManager mMockCompanionTransportManager;
|
||||
@Mock
|
||||
private CrossDeviceCall mMockCrossDeviceCall;
|
||||
@Mock
|
||||
private TelecomManager mMockTelecomManager;
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -54,6 +67,12 @@ public class CrossDeviceSyncControllerTest {
|
||||
mCrossDeviceSyncController = new CrossDeviceSyncController(
|
||||
InstrumentationRegistry.getInstrumentation().getContext(),
|
||||
mMockCompanionTransportManager);
|
||||
when(mMockContext.getSystemServiceName(TelecomManager.class))
|
||||
.thenReturn(Context.TELECOM_SERVICE);
|
||||
when(mMockContext.getSystemService(Context.TELECOM_SERVICE))
|
||||
.thenReturn(mMockTelecomManager);
|
||||
doNothing().when(mMockTelecomManager).registerPhoneAccount(any());
|
||||
doNothing().when(mMockTelecomManager).unregisterPhoneAccount(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,8 +83,10 @@ public class CrossDeviceSyncControllerTest {
|
||||
mCrossDeviceSyncController.processTelecomDataFromSync(data);
|
||||
assertWithMessage("Unexpectedly found a call").that(
|
||||
callMetadataSyncData.getCalls()).isEmpty();
|
||||
assertWithMessage("Unexpectedly found a request").that(
|
||||
callMetadataSyncData.getRequests()).isEmpty();
|
||||
assertWithMessage("Unexpectedly has control requests").that(
|
||||
callMetadataSyncData.getCallControlRequests()).isEmpty();
|
||||
assertWithMessage("Unexpectedly has create requests").that(
|
||||
callMetadataSyncData.getCallCreateRequests()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,8 +96,10 @@ public class CrossDeviceSyncControllerTest {
|
||||
mCrossDeviceSyncController.processTelecomDataFromSync(data);
|
||||
assertWithMessage("Unexpectedly found a call").that(
|
||||
callMetadataSyncData.getCalls()).isEmpty();
|
||||
assertWithMessage("Unexpectedly found a request").that(
|
||||
callMetadataSyncData.getRequests()).isEmpty();
|
||||
assertWithMessage("Unexpectedly has control requests").that(
|
||||
callMetadataSyncData.getCallControlRequests()).isEmpty();
|
||||
assertWithMessage("Unexpectedly has create requests").that(
|
||||
callMetadataSyncData.getCallCreateRequests()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,6 +109,8 @@ public class CrossDeviceSyncControllerTest {
|
||||
when(mMockCrossDeviceCall.getReadableCallerId(anyBoolean())).thenReturn(callerId);
|
||||
final String appName = "AppName";
|
||||
when(mMockCrossDeviceCall.getCallingAppName()).thenReturn(appName);
|
||||
final String pkgName = "com.google.test";
|
||||
when(mMockCrossDeviceCall.getCallingAppPackageName()).thenReturn(pkgName);
|
||||
final String appIcon = "ABCD";
|
||||
when(mMockCrossDeviceCall.getCallingAppIcon()).thenReturn(appIcon.getBytes());
|
||||
when(mMockCrossDeviceCall.getStatus()).thenReturn(android.companion.Telecom.Call.RINGING);
|
||||
@@ -105,32 +130,85 @@ public class CrossDeviceSyncControllerTest {
|
||||
callMetadataSyncData.getCalls().stream().findAny().orElseThrow();
|
||||
assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
|
||||
assertWithMessage("Wrong app icon").that(new String(call.getAppIcon())).isEqualTo(appIcon);
|
||||
assertWithMessage("Wrong app name").that(call.getAppName()).isEqualTo(appName);
|
||||
final CallMetadataSyncData.CallFacilitator facilitator = call.getFacilitator();
|
||||
assertWithMessage("Wrong app name").that(facilitator.getName()).isEqualTo(appName);
|
||||
assertWithMessage("Wrong pkg name").that(facilitator.getIdentifier()).isEqualTo(pkgName);
|
||||
assertWithMessage("Wrong caller id").that(call.getCallerId()).isEqualTo(callerId);
|
||||
assertWithMessage("Wrong status").that(call.getStatus())
|
||||
.isEqualTo(android.companion.Telecom.Call.RINGING);
|
||||
assertWithMessage("Wrong controls").that(call.getControls()).isEqualTo(controls);
|
||||
assertWithMessage("Unexpectedly has control requests").that(
|
||||
callMetadataSyncData.getCallControlRequests()).isEmpty();
|
||||
assertWithMessage("Unexpectedly has create requests").that(
|
||||
callMetadataSyncData.getCallCreateRequests()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() {
|
||||
final byte[] data = CrossDeviceSyncController.createCallControlMessage(
|
||||
/* callId= */ "123abc", /* status= */ android.companion.Telecom.ACCEPT);
|
||||
/* callId= */ "5678abc", /* status= */ android.companion.Telecom.ACCEPT);
|
||||
final CallMetadataSyncData callMetadataSyncData =
|
||||
mCrossDeviceSyncController.processTelecomDataFromSync(data);
|
||||
assertWithMessage("Wrong number of requests").that(
|
||||
callMetadataSyncData.getRequests()).hasSize(1);
|
||||
final CallMetadataSyncData.Call call =
|
||||
callMetadataSyncData.getRequests().stream().findAny().orElseThrow();
|
||||
assertWithMessage("Wrong id").that(call.getId()).isEqualTo("123abc");
|
||||
assertWithMessage("Wrong app icon").that(call.getAppIcon()).isNull();
|
||||
assertWithMessage("Wrong app name").that(call.getAppName()).isNull();
|
||||
assertWithMessage("Wrong caller id").that(call.getCallerId()).isNull();
|
||||
assertWithMessage("Wrong status").that(call.getStatus())
|
||||
.isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
|
||||
assertWithMessage("Wrong controls").that(call.getControls())
|
||||
.isEqualTo(Set.of(android.companion.Telecom.ACCEPT));
|
||||
callMetadataSyncData.getCallControlRequests()).hasSize(1);
|
||||
final CallMetadataSyncData.CallControlRequest request =
|
||||
callMetadataSyncData.getCallControlRequests().stream().findAny().orElseThrow();
|
||||
assertWithMessage("Wrong id").that(request.getId()).isEqualTo("5678abc");
|
||||
assertWithMessage("Wrong control").that(request.getControl())
|
||||
.isEqualTo(android.companion.Telecom.ACCEPT);
|
||||
assertWithMessage("Unexpectedly has active calls").that(
|
||||
callMetadataSyncData.getCalls()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPhoneAccount_success() {
|
||||
final PhoneAccount phoneAccount =
|
||||
CrossDeviceSyncController.PhoneAccountManager.createPhoneAccount(
|
||||
new PhoneAccountHandle(
|
||||
new ComponentName("com.google.test", "com.google.test.Activity"),
|
||||
"id"), "Test App", "com.google.test");
|
||||
assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFacilitators_alreadyExists_doesNotCreateAnother() {
|
||||
final CrossDeviceSyncController.PhoneAccountManager phoneAccountManager =
|
||||
new CrossDeviceSyncController.PhoneAccountManager(mMockContext);
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
callMetadataSyncData.addFacilitator(
|
||||
new CallMetadataSyncData.CallFacilitator("name", "com.google.test"));
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData);
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData);
|
||||
verify(mMockTelecomManager, times(1)).registerPhoneAccount(any());
|
||||
verify(mMockTelecomManager, times(0)).unregisterPhoneAccount(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFacilitators_new_addsIt() {
|
||||
final CrossDeviceSyncController.PhoneAccountManager phoneAccountManager =
|
||||
new CrossDeviceSyncController.PhoneAccountManager(mMockContext);
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
callMetadataSyncData.addFacilitator(
|
||||
new CallMetadataSyncData.CallFacilitator("name", "com.google.test"));
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData);
|
||||
callMetadataSyncData.addFacilitator(
|
||||
new CallMetadataSyncData.CallFacilitator("name", "com.google.test2"));
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData);
|
||||
verify(mMockTelecomManager, times(2)).registerPhoneAccount(any());
|
||||
verify(mMockTelecomManager, times(0)).unregisterPhoneAccount(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFacilitators_old_removesIt() {
|
||||
final CrossDeviceSyncController.PhoneAccountManager phoneAccountManager =
|
||||
new CrossDeviceSyncController.PhoneAccountManager(mMockContext);
|
||||
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
|
||||
callMetadataSyncData.addFacilitator(
|
||||
new CallMetadataSyncData.CallFacilitator("name", "com.google.test"));
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData);
|
||||
final CallMetadataSyncData callMetadataSyncData2 = new CallMetadataSyncData();
|
||||
phoneAccountManager.updateFacilitators(0, callMetadataSyncData2);
|
||||
verify(mMockTelecomManager, times(1)).registerPhoneAccount(any());
|
||||
verify(mMockTelecomManager, times(1)).unregisterPhoneAccount(any());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user