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

Commit ebaeb8b4 authored by Christine Franks's avatar Christine Franks
Browse files

Fix outgoing synced calls

Adds a dialing state and incoming/outgoing direction differentiation,
reworks silence and mute state handling, and introduces the idea of a
synced call's "owner" (non-owners should not sync their replicas to
others, to avoid infinte replication of outgoing calls).

Additionally, use Bundle rather than Parcelable for passing call data,
as parcelables can throw exceptions due to a Telecom race condition.

Bug: 285079998
Bug: 285080418
Test: atest

Change-Id: I50a601637174608a5aaa05b0a5378c3bdd323aa5
parent 11230c84
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -45,10 +45,18 @@ message Telecom {
      AUDIO_PROCESSING = 5;
      RINGING_SIMULATED = 6;
      DISCONNECTED = 7;
      DIALING = 8;
    }
    Status status = 3;

    repeated Control controls = 4;

    enum Direction {
      UNKNOWN_DIRECTION = 0;
      INCOMING = 1;
      OUTGOING = 2;
    }
    Direction direction = 5;
  }

  message Request {
+24 −0
Original line number Diff line number Diff line
@@ -1415,6 +1415,30 @@ public class CompanionDeviceManagerService extends SystemService {
                mCrossDeviceSyncController.syncMessageToDevice(associationId, message);
            }
        }

        @Override
        public void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message) {
            if (CompanionDeviceConfig.isEnabled(
                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
                mCrossDeviceSyncController.syncMessageToAllDevicesForUserId(userId, message);
            }
        }

        @Override
        public void addSelfOwnedCallId(String callId) {
            if (CompanionDeviceConfig.isEnabled(
                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
                mCrossDeviceSyncController.addSelfOwnedCallId(callId);
            }
        }

        @Override
        public void removeSelfOwnedCallId(String callId) {
            if (CompanionDeviceConfig.isEnabled(
                    CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
                mCrossDeviceSyncController.removeSelfOwnedCallId(callId);
            }
        }
    }

    /**
+9 −0
Original line number Diff line number Diff line
@@ -45,6 +45,15 @@ public interface CompanionDeviceManagerServiceInternal {
     */
    void sendCrossDeviceSyncMessage(int associationId, byte[] message);

    /** Sends the provided message to all active associations for the specified user. */
    void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message);

    /** Mark a call id as "self owned" (i.e. this device owns the canonical call). */
    void addSelfOwnedCallId(String callId);

    /** Unmark a call id as "self owned" (i.e. this device no longer owns the canonical call). */
    void removeSelfOwnedCallId(String callId);

    /**
     * Requests a sync from an InCallService to CDM, for the given user and call metadata.
     */
+67 −13
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ import com.android.server.companion.CompanionDeviceManagerServiceInternal;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.Set;

/** Service for Telecom to bind to when call metadata is synced between devices. */
public class CallMetadataSyncConnectionService extends ConnectionService {
@@ -65,11 +65,32 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
                                        associationId, call.getId()));
                        if (existingConnection != null) {
                            existingConnection.update(call);
                        } else {
                            // Check if this is an in-progress id being finalized.
                            CallMetadataSyncConnectionIdentifier key = null;
                            for (Map.Entry<CallMetadataSyncConnectionIdentifier,
                                    CallMetadataSyncConnection> e : mActiveConnections.entrySet()) {
                                if (e.getValue().getAssociationId() == associationId
                                        && !e.getValue().isIdFinalized()
                                        && call.getId().endsWith(e.getValue().getCallId())) {
                                    key = e.getKey();
                                    break;
                                }
                            }
                            if (key != null) {
                                final CallMetadataSyncConnection connection =
                                        mActiveConnections.remove(key);
                                connection.update(call);
                                mActiveConnections.put(
                                        new CallMetadataSyncConnectionIdentifier(associationId,
                                                call.getId()), connection);
                            }
                        }
                    }
                    // Remove obsolete calls.
                    mActiveConnections.values().removeIf(connection -> {
                        if (associationId == connection.getAssociationId()
                        if (connection.isIdFinalized()
                                && associationId == connection.getAssociationId()
                                && !callMetadataSyncData.hasCall(connection.getCallId())) {
                            connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
                            return true;
@@ -77,6 +98,17 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
                        return false;
                    });
                }

                @Override
                void cleanUpCallIds(Set<String> callIds) {
                    mActiveConnections.values().removeIf(connection -> {
                        if (callIds.contains(connection.getCallId())) {
                            connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
                            return true;
                        }
                        return false;
                    });
                }
            };

    @Override
@@ -95,10 +127,9 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
            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);
        // 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.
        final CallMetadataSyncData.Call call = CallMetadataSyncData.Call.fromBundle(
                connectionRequest.getExtras().getBundle(CrossDeviceSyncController.EXTRA_CALL));
        call.setDirection(android.companion.Telecom.Call.INCOMING);
        connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
        connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID);
        connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
@@ -130,18 +161,26 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
    @Override
    public Connection onCreateOutgoingConnection(PhoneAccountHandle phoneAccountHandle,
            ConnectionRequest connectionRequest) {
        final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
        final PhoneAccountHandle handle = phoneAccountHandle != null ? phoneAccountHandle
                : connectionRequest.getAccountHandle();
        final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);

        final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
        call.setId(UUID.randomUUID().toString());
        call.setId(
                connectionRequest.getExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID));
        call.setStatus(android.companion.Telecom.Call.UNKNOWN_STATUS);
        final CallMetadataSyncData.CallFacilitator callFacilitator =
                new CallMetadataSyncData.CallFacilitator(phoneAccount.getLabel().toString(),
                        phoneAccount.getExtras().getString(
                                CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID));
                new CallMetadataSyncData.CallFacilitator(phoneAccount != null
                        ? phoneAccount.getLabel().toString()
                        : handle.getComponentName().getShortClassName(),
                        phoneAccount != null ? phoneAccount.getExtras().getString(
                                CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID)
                                : handle.getComponentName().getPackageName());
        call.setFacilitator(callFacilitator);
        call.setDirection(android.companion.Telecom.Call.OUTGOING);
        call.setCallerId(connectionRequest.getAddress().getSchemeSpecificPart());

        final int associationId = connectionRequest.getExtras().getInt(
        final int associationId = phoneAccount.getExtras().getInt(
                CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);

        connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
@@ -160,13 +199,15 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
                                CrossDeviceSyncController.createCallControlMessage(callId, action));
                    }
                });
        connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
        connection.setCallerDisplayName(call.getCallerId(), TelecomManager.PRESENTATION_ALLOWED);

        mCdmsi.addSelfOwnedCallId(call.getId());
        mCdmsi.sendCrossDeviceSyncMessage(associationId,
                CrossDeviceSyncController.createCallCreateMessage(call.getId(),
                        connectionRequest.getAddress().toString(),
                        call.getFacilitator().getIdentifier()));

        connection.setInitializing();
        return connection;
    }

@@ -240,6 +281,7 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
        private final int mAssociationId;
        private final CallMetadataSyncData.Call mCall;
        private final CallMetadataSyncConnectionCallback mCallback;
        private boolean mIsIdFinalized;

        CallMetadataSyncConnection(TelecomManager telecomManager, AudioManager audioManager,
                int associationId, CallMetadataSyncData.Call call,
@@ -259,6 +301,10 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
            return mAssociationId;
        }

        public boolean isIdFinalized() {
            return mIsIdFinalized;
        }

        private void initialize() {
            final int status = mCall.getStatus();
            if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -273,6 +319,8 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
                setOnHold();
            } else if (state == Call.STATE_DISCONNECTED) {
                setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
            } else if (state == Call.STATE_DIALING) {
                setDialing();
            } else {
                setInitialized();
            }
@@ -307,6 +355,10 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
        }

        private void update(CallMetadataSyncData.Call call) {
            if (!mIsIdFinalized) {
                mCall.setId(call.getId());
                mIsIdFinalized = true;
            }
            final int status = call.getStatus();
            if (status == android.companion.Telecom.Call.RINGING_SILENCED
                    && mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -323,6 +375,8 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
                    setOnHold();
                } else if (state == Call.STATE_DISCONNECTED) {
                    setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
                } else if (state == Call.STATE_DIALING) {
                    setDialing();
                } else {
                    Slog.e(TAG, "Could not update call to unknown state");
                }
+56 −72
Original line number Diff line number Diff line
@@ -16,10 +16,8 @@

package com.android.server.companion.datatransfer.contextsync;

import android.annotation.NonNull;
import android.companion.ContextSyncMessage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.Collection;
@@ -74,9 +72,10 @@ class CallMetadataSyncData {
        return mCallFacilitators;
    }

    public static class CallFacilitator implements Parcelable {
    public static class CallFacilitator {
        private String mName;
        private String mIdentifier;
        private boolean mIsTel;

        CallFacilitator() {}

@@ -85,16 +84,6 @@ class CallMetadataSyncData {
            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;
        }
@@ -103,6 +92,10 @@ class CallMetadataSyncData {
            return mIdentifier;
        }

        public boolean isTel() {
            return mIsTel;
        }

        public void setName(String name) {
            mName = name;
        }
@@ -111,25 +104,9 @@ class CallMetadataSyncData {
            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 void setIsTel(boolean isTel) {
            mIsTel = isTel;
        }
                };
    }

    public static class CallControlRequest {
@@ -183,40 +160,57 @@ class CallMetadataSyncData {
        }
    }

    public static class Call implements Parcelable {
    public static class Call {

        private static final String EXTRA_CALLER_ID =
                "com.android.server.companion.datatransfer.contextsync.extra.CALLER_ID";
        private static final String EXTRA_APP_ICON =
                "com.android.server.companion.datatransfer.contextsync.extra.APP_ICON";
        private static final String EXTRA_FACILITATOR_NAME =
                "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_NAME";
        private static final String EXTRA_FACILITATOR_ID =
                "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_ID";
        private static final String EXTRA_STATUS =
                "com.android.server.companion.datatransfer.contextsync.extra.STATUS";
        private static final String EXTRA_DIRECTION =
                "com.android.server.companion.datatransfer.contextsync.extra.DIRECTION";
        private static final String EXTRA_CONTROLS =
                "com.android.server.companion.datatransfer.contextsync.extra.CONTROLS";
        private String mId;
        private String mCallerId;
        private byte[] mAppIcon;
        private CallFacilitator mFacilitator;
        private int mStatus;
        private int mDirection;
        private final Set<Integer> mControls = new HashSet<>();

        public static Call fromParcel(Parcel parcel) {
        public static Call fromBundle(Bundle bundle) {
            final Call call = new Call();
            call.setId(parcel.readString());
            call.setCallerId(parcel.readString());
            call.setAppIcon(parcel.readBlob());
            call.setFacilitator(parcel.readParcelable(CallFacilitator.class.getClassLoader(),
                    CallFacilitator.class));
            call.setStatus(parcel.readInt());
            final int numberOfControls = parcel.readInt();
            for (int i = 0; i < numberOfControls; i++) {
                call.addControl(parcel.readInt());
            if (bundle != null) {
                call.setId(bundle.getString(CrossDeviceSyncController.EXTRA_CALL_ID));
                call.setCallerId(bundle.getString(EXTRA_CALLER_ID));
                call.setAppIcon(bundle.getByteArray(EXTRA_APP_ICON));
                final String facilitatorName = bundle.getString(EXTRA_FACILITATOR_NAME);
                final String facilitatorIdentifier = bundle.getString(EXTRA_FACILITATOR_ID);
                call.setFacilitator(new CallFacilitator(facilitatorName, facilitatorIdentifier));
                call.setStatus(bundle.getInt(EXTRA_STATUS));
                call.setDirection(bundle.getInt(EXTRA_DIRECTION));
                call.setControls(new HashSet<>(bundle.getIntegerArrayList(EXTRA_CONTROLS)));
            }
            return call;
        }

        @Override
        public void writeToParcel(Parcel parcel, int parcelableFlags) {
            parcel.writeString(mId);
            parcel.writeString(mCallerId);
            parcel.writeBlob(mAppIcon);
            parcel.writeParcelable(mFacilitator, parcelableFlags);
            parcel.writeInt(mStatus);
            parcel.writeInt(mControls.size());
            for (int control : mControls) {
                parcel.writeInt(control);
            }
        public Bundle writeToBundle() {
            final Bundle bundle = new Bundle();
            bundle.putString(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
            bundle.putString(EXTRA_CALLER_ID, mCallerId);
            bundle.putByteArray(EXTRA_APP_ICON, mAppIcon);
            bundle.putString(EXTRA_FACILITATOR_NAME, mFacilitator.getName());
            bundle.putString(EXTRA_FACILITATOR_ID, mFacilitator.getIdentifier());
            bundle.putInt(EXTRA_STATUS, mStatus);
            bundle.putInt(EXTRA_DIRECTION, mDirection);
            bundle.putIntegerArrayList(EXTRA_CONTROLS, new ArrayList<>(mControls));
            return bundle;
        }

        void setId(String id) {
@@ -239,6 +233,10 @@ class CallMetadataSyncData {
            mStatus = status;
        }

        void setDirection(int direction) {
            mDirection = direction;
        }

        void addControl(int control) {
            mControls.add(control);
        }
@@ -268,6 +266,10 @@ class CallMetadataSyncData {
            return mStatus;
        }

        int getDirection() {
            return mDirection;
        }

        Set<Integer> getControls() {
            return mControls;
        }
@@ -288,23 +290,5 @@ class CallMetadataSyncData {
        public int hashCode() {
            return Objects.hashCode(mId);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @NonNull public static final Parcelable.Creator<Call> CREATOR = new Parcelable.Creator<>() {

            @Override
            public Call createFromParcel(Parcel source) {
                return Call.fromParcel(source);
            }

            @Override
            public Call[] newArray(int size) {
                return new Call[size];
            }
        };
    }
}
Loading