Merge "Process synced messages from secure channel" into udc-dev

This commit is contained in:
Christine Franks
2023-04-21 02:16:00 +00:00
committed by Android (Google) Code Review
2 changed files with 276 additions and 15 deletions

View File

@@ -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) {
mTelecomManager.registerPhoneAccount(phoneAccount);
mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(packageName), true);
}
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(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);
}
}
}

View File

@@ -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();
}