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

Commit 81f10863 authored by Christine Franks's avatar Christine Franks Committed by Android (Google) Code Review
Browse files

Merge "Add new control operations for call metadata"

parents 89708708 b89a0d3a
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();
    }
}