Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d8ec94a authored by Christine Franks's avatar Christine Franks
Browse files

Split and streamline create and control requests

Bug: 265466098
Test: atest FrameworksServicesTests:com.android.server.companion.datatransfer.contextsync
Change-Id: Iba859c4c9e712f4d3f00fed1b574c6137dd3a362
parent b58b4602
Loading
Loading
Loading
Loading
+36 −6
Original line number Diff line number Diff line
@@ -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;
}
+129 −73
Original line number Diff line number Diff line
@@ -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);
    }

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

    @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;
    @Override
    public void onCreateIncomingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
            ConnectionRequest connectionRequest) {
        Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
    }

        public int getAssociationId() {
            return mAssociationId;
    @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);

        public String getAppIdentifier() {
            return mAppIdentifier;
        mCdmsi.sendCrossDeviceSyncMessage(associationId,
                CrossDeviceSyncController.createCallCreateMessage(call.getId(),
                        connectionRequest.getAddress().toString(),
                        call.getFacilitator().getIdentifier()));

        return connection;
    }

    @Override
        public int hashCode() {
            return Objects.hash(mAssociationId, mAppIdentifier);
    public void onCreateOutgoingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
            ConnectionRequest connectionRequest) {
        Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
    }

    @Override
        public boolean equals(Object other) {
            if (other instanceof PhoneAccountHandleIdentifier) {
                return ((PhoneAccountHandleIdentifier) other).getAssociationId() == mAssociationId
                        && mAppIdentifier != null
                        && mAppIdentifier.equals(
                        ((PhoneAccountHandleIdentifier) other).getAppIdentifier());
            }
            return false;
    public void onCreateConnectionComplete(Connection connection) {
        if (connection instanceof CallMetadataSyncConnection) {
            ((CallMetadataSyncConnection) connection).initialize();
        }
    }

    private static final class CallMetadataSyncConnectionIdentifier {
    @VisibleForTesting
    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;
+150 −19
Original line number Diff line number Diff line
@@ -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() {
+4 −9
Original line number Diff line number Diff line
@@ -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();
                    }
                }
+319 −13

File changed.

Preview size limit exceeded, changes collapsed.

Loading