Loading services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java +116 −28 Original line number Diff line number Diff line Loading @@ -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 services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java 0 → 100644 +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(); } } Loading
services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java +116 −28 Original line number Diff line number Diff line Loading @@ -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
services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java 0 → 100644 +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(); } }