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

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

Add new control operations for call metadata

Bug: 266152297
Test: atest FrameworksServicesTests:com.android.server.companion.datatransfer

Change-Id: I320c0ab73073f107ed69341102f0622a3c519ea8
parent 2b9edf63
Loading
Loading
Loading
Loading
+116 −28
Original line number Diff line number Diff line
@@ -16,60 +16,148 @@

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

import android.annotation.Nullable;
import android.telecom.Call;
import android.telecom.InCallService;
import android.telecom.TelecomManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.companion.CompanionDeviceConfig;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/** In-call service to sync call metadata across a user's devices. */
/**
 * In-call service to sync call metadata across a user's devices. Note that mute and silence are
 * global states and apply to all current calls.
 */
public class CallMetadataSyncInCallService extends InCallService {

    private static final long NOT_VALID = -1L;

    @VisibleForTesting
    final Set<CrossDeviceCall> mCurrentCalls = new HashSet<>();
    final Map<Call, CrossDeviceCall> mCurrentCalls = new HashMap<>();
    final Call.Callback mTelecomCallback = new Call.Callback() {
        @Override
        public void onDetailsChanged(Call call, Call.Details details) {
            mCurrentCalls.get(call).updateCallDetails(details);
        }
    };
    final CallMetadataSyncCallback mCallMetadataSyncCallback = new CallMetadataSyncCallback() {
        @Override
        void processCallControlAction(int crossDeviceCallId, int callControlAction) {
            final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
                    mCurrentCalls.values());
            switch (callControlAction) {
                case android.companion.Telecom.Call.ACCEPT:
                    if (crossDeviceCall != null) {
                        crossDeviceCall.doAccept();
                    }
                    break;
                case android.companion.Telecom.Call.REJECT:
                    if (crossDeviceCall != null) {
                        crossDeviceCall.doReject();
                    }
                    break;
                case android.companion.Telecom.Call.SILENCE:
                    doSilence();
                    break;
                case android.companion.Telecom.Call.MUTE:
                    doMute();
                    break;
                case android.companion.Telecom.Call.UNMUTE:
                    doUnmute();
                    break;
                case android.companion.Telecom.Call.END:
                    if (crossDeviceCall != null) {
                        crossDeviceCall.doEnd();
                    }
                    break;
                case android.companion.Telecom.Call.PUT_ON_HOLD:
                    if (crossDeviceCall != null) {
                        crossDeviceCall.doPutOnHold();
                    }
                    break;
                case android.companion.Telecom.Call.TAKE_OFF_HOLD:
                    if (crossDeviceCall != null) {
                        crossDeviceCall.doTakeOffHold();
                    }
                    break;
                default:
            }
        }

        @Override
        void requestCrossDeviceSync(int userId) {
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mCurrentCalls.addAll(getCalls().stream().map(CrossDeviceCall::new).toList());
        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
            mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call,
                    call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState()))));
        }
    }

    @Nullable
    @VisibleForTesting
    CrossDeviceCall getCallForId(long crossDeviceCallId, Collection<CrossDeviceCall> calls) {
        if (crossDeviceCallId == NOT_VALID) {
            return null;
        }
        for (CrossDeviceCall crossDeviceCall : calls) {
            if (crossDeviceCall.getId() == crossDeviceCallId) {
                return crossDeviceCall;
            }
        }
        return null;
    }

    @Override
    public void onCallAdded(Call call) {
        onCallAdded(new CrossDeviceCall(call));
        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
            mCurrentCalls.put(call,
                    new CrossDeviceCall(getPackageManager(), call, getCallAudioState()));
        }

    @VisibleForTesting
    void onCallAdded(CrossDeviceCall call) {
        mCurrentCalls.add(call);
    }

    @Override
    public void onCallRemoved(Call call) {
        mCurrentCalls.removeIf(crossDeviceCall -> crossDeviceCall.getCall().equals(call));
        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
            mCurrentCalls.remove(call);
        }
    }

    /** Data holder for a telecom call and additional metadata. */
    public static final class CrossDeviceCall {
        private static final AtomicLong sNextId = new AtomicLong(1);
    @Override
    public void onMuteStateChanged(boolean isMuted) {
        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
            mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted));
        }
    }

        private final Call mCall;
        private final long mId;
    @Override
    public void onSilenceRinger() {
        if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
            mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging());
        }
    }

        public CrossDeviceCall(Call call) {
            mCall = call;
            mId = sNextId.getAndIncrement();
    private void doMute() {
        setMuted(/* shouldMute= */ true);
    }

        public Call getCall() {
            return mCall;
    private void doUnmute() {
        setMuted(/* shouldMute= */ false);
    }

        public long getId() {
            return mId;
    private void doSilence() {
        final TelecomManager telecomManager = getSystemService(TelecomManager.class);
        if (telecomManager != null) {
            telecomManager.silenceRinger();
        }
    }
}
 No newline at end of file
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.Mockito.when;

import android.platform.test.annotations.Presubmit;
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;

import java.util.List;

@Presubmit
@RunWith(AndroidTestingRunner.class)
public class CallMetadataSyncInCallServiceTest {

    private CallMetadataSyncInCallService mSyncInCallService;
    @Mock
    private CrossDeviceCall mMockCrossDeviceCall;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mSyncInCallService = new CallMetadataSyncInCallService();
    }

    @Test
    public void getCallForId_invalid() {
        when(mMockCrossDeviceCall.getId()).thenReturn(-1L);
        final CrossDeviceCall call = mSyncInCallService.getCallForId(-1L,
                List.of(mMockCrossDeviceCall));
        assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
    }

    @Test
    public void getCallForId_noMatch() {
        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
        final CrossDeviceCall call = mSyncInCallService.getCallForId(1L,
                List.of(mMockCrossDeviceCall));
        assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
    }

    @Test
    public void getCallForId_hasMatch() {
        when(mMockCrossDeviceCall.getId()).thenReturn(5L);
        final CrossDeviceCall call = mSyncInCallService.getCallForId(5L,
                List.of(mMockCrossDeviceCall));
        assertWithMessage("Unexpectedly did not find a match for call id").that(call).isNotNull();
    }
}