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

Commit 0e125855 authored by Christine Franks's avatar Christine Franks Committed by Automerger Merge Worker
Browse files

Merge "Process synced messages from secure channel" into udc-dev am: 0dd7f1f2 am: e22fef35

parents 0ac96031 e22fef35
Loading
Loading
Loading
Loading
+267 −12
Original line number Diff line number Diff line
@@ -17,26 +17,38 @@
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.ConnectionService;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/** Service for Telecom to bind to when call metadata is synced between devices. */
public class CallMetadataSyncConnectionService extends ConnectionService {

    private static final String TAG = "CallMetadataSyncConnectionService";

    private AudioManager mAudioManager;
    private TelecomManager mTelecomManager;
    private final Map<String, PhoneAccountHandle> mPhoneAccountHandles = new HashMap<>();
    private final Map<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
            new HashMap<>();

    @Override
    public void onCreate() {
        super.onCreate();

        mAudioManager = getSystemService(AudioManager.class);
        mTelecomManager = getSystemService(TelecomManager.class);
    }

@@ -44,34 +56,277 @@ public class CallMetadataSyncConnectionService extends ConnectionService {
     * Registers a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
     * device.
     */
    public void registerPhoneAccount(String packageName, String humanReadableAppName) {
        final PhoneAccount phoneAccount = createPhoneAccount(packageName, humanReadableAppName);
        if (phoneAccount != null) {
    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(packageName), true);
        }
        mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(phoneAccountHandleIdentifier),
                true);
    }

    /**
     * Unregisters a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
     * device.
     */
    public void unregisterPhoneAccount(String packageName) {
        mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(packageName));
    private void unregisterPhoneAccount(int associationId, String appIdentifier) {
        mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(
                new PhoneAccountHandleIdentifier(associationId, appIdentifier)));
    }

    @VisibleForTesting
    PhoneAccount createPhoneAccount(String packageName, String humanReadableAppName) {
        if (mPhoneAccountHandles.containsKey(packageName)) {
    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(packageName, handle);
        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 {
        private final int mAssociationId;
        private final long mCallId;

        CallMetadataSyncConnectionIdentifier(int associationId, long callId) {
            mAssociationId = associationId;
            mCallId = callId;
        }

        public int getAssociationId() {
            return mAssociationId;
        }

        public long getCallId() {
            return mCallId;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mAssociationId, mCallId);
        }

        @Override
        public boolean equals(Object other) {
            if (other instanceof CallMetadataSyncConnectionIdentifier) {
                return ((CallMetadataSyncConnectionIdentifier) other).getAssociationId()
                        == mAssociationId
                        && (((CallMetadataSyncConnectionIdentifier) other).getCallId() == mCallId);
            }
            return false;
        }
    }

    private abstract static class CallMetadataSyncConnectionCallback {

        abstract void sendCallAction(int associationId, long callId, int action);

        abstract void sendStateChange(int associationId, long callId, int newState);
    }

    private static class CallMetadataSyncConnection extends Connection {

        private final TelecomManager mTelecomManager;
        private final AudioManager mAudioManager;
        private final int mAssociationId;
        private final CallMetadataSyncData.Call mCall;
        private final CallMetadataSyncConnectionCallback mCallback;

        CallMetadataSyncConnection(TelecomManager telecomManager, AudioManager audioManager,
                int associationId, CallMetadataSyncData.Call call,
                CallMetadataSyncConnectionCallback callback) {
            mTelecomManager = telecomManager;
            mAudioManager = audioManager;
            mAssociationId = associationId;
            mCall = call;
            mCallback = callback;
        }

        public long getCallId() {
            return mCall.getId();
        }

        public void initialize() {
            final int status = mCall.getStatus();
            if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
                mTelecomManager.silenceRinger();
            }
            final int state = CrossDeviceCall.convertStatusToState(status);
            if (state == Call.STATE_RINGING) {
                setRinging();
            } else if (state == Call.STATE_ACTIVE) {
                setActive();
            } else if (state == Call.STATE_HOLDING) {
                setOnHold();
            } else {
                Slog.e(TAG, "Could not initialize call to unknown state");
            }

            final Bundle extras = new Bundle();
            extras.putLong(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
            putExtras(extras);

            int capabilities = getConnectionCapabilities();
            if (mCall.hasControl(android.companion.Telecom.Call.PUT_ON_HOLD)) {
                capabilities |= CAPABILITY_HOLD;
            } else {
                capabilities &= ~CAPABILITY_HOLD;
            }
            if (mCall.hasControl(android.companion.Telecom.Call.MUTE)) {
                capabilities |= CAPABILITY_MUTE;
            } else {
                capabilities &= ~CAPABILITY_MUTE;
            }
            mAudioManager.setMicrophoneMute(
                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
            if (capabilities != getConnectionCapabilities()) {
                setConnectionCapabilities(capabilities);
            }
        }

        public void update(CallMetadataSyncData.Call call) {
            final int status = call.getStatus();
            if (status == android.companion.Telecom.Call.RINGING_SILENCED
                    && mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
                mTelecomManager.silenceRinger();
            }
            mCall.setStatus(status);
            final int state = CrossDeviceCall.convertStatusToState(status);
            if (state != getState()) {
                if (state == Call.STATE_RINGING) {
                    setRinging();
                } else if (state == Call.STATE_ACTIVE) {
                    setActive();
                } else if (state == Call.STATE_HOLDING) {
                    setOnHold();
                } else {
                    Slog.e(TAG, "Could not update call to unknown state");
                }
            }

            int capabilities = getConnectionCapabilities();
            final boolean hasHoldControl = mCall.hasControl(
                    android.companion.Telecom.Call.PUT_ON_HOLD)
                    || mCall.hasControl(android.companion.Telecom.Call.TAKE_OFF_HOLD);
            if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
                    == CAPABILITY_HOLD)) {
                if (hasHoldControl) {
                    capabilities |= CAPABILITY_HOLD;
                } else {
                    capabilities &= ~CAPABILITY_HOLD;
                }
            }
            final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.Call.MUTE);
            if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
                    == CAPABILITY_MUTE)) {
                if (hasMuteControl) {
                    capabilities |= CAPABILITY_MUTE;
                } else {
                    capabilities &= ~CAPABILITY_MUTE;
                }
            }
            mAudioManager.setMicrophoneMute(
                    mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
            if (capabilities != getConnectionCapabilities()) {
                setConnectionCapabilities(capabilities);
            }
        }

        @Override
        public void onAnswer(int videoState) {
            sendCallAction(android.companion.Telecom.Call.ACCEPT);
        }

        @Override
        public void onReject() {
            sendCallAction(android.companion.Telecom.Call.REJECT);
        }

        @Override
        public void onReject(int rejectReason) {
            onReject();
        }

        @Override
        public void onReject(String replyMessage) {
            onReject();
        }

        @Override
        public void onSilence() {
            sendCallAction(android.companion.Telecom.Call.SILENCE);
        }

        @Override
        public void onHold() {
            sendCallAction(android.companion.Telecom.Call.PUT_ON_HOLD);
        }

        @Override
        public void onUnhold() {
            sendCallAction(android.companion.Telecom.Call.TAKE_OFF_HOLD);
        }

        @Override
        public void onMuteStateChanged(boolean isMuted) {
            sendCallAction(isMuted ? android.companion.Telecom.Call.MUTE
                    : android.companion.Telecom.Call.UNMUTE);
        }

        @Override
        public void onDisconnect() {
            sendCallAction(android.companion.Telecom.Call.END);
        }

        @Override
        public void onStateChanged(int state) {
            mCallback.sendStateChange(mAssociationId, mCall.getId(), state);
        }

        private void sendCallAction(int action) {
            mCallback.sendCallAction(mAssociationId, mCall.getId(), action);
        }
    }
}
+9 −3
Original line number Diff line number Diff line
@@ -45,16 +45,22 @@ public class CallMetadataSyncConnectionServiceTest {
    @Test
    public void createPhoneAccount_success() {
        final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
                "com.google.test", "Test App");
                new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
                associationId= */
                        0, "com.google.test"), "Test App");
        assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
    }

    @Test
    public void createPhoneAccount_alreadyExists_doesNotCreateAnother() {
        final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
                "com.google.test", "Test App");
                new CallMetadataSyncConnectionService.PhoneAccountHandleIdentifier(/*
                associationId= */
                        0, "com.google.test"), "Test App");
        final PhoneAccount phoneAccount2 = mSyncConnectionService.createPhoneAccount(
                "com.google.test", "Test App #2");
                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();
    }