Loading services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java +267 −12 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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); } } } services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java +9 −3 Original line number Diff line number Diff line Loading @@ -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(); } Loading Loading
services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java +267 −12 Original line number Diff line number Diff line Loading @@ -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); } Loading @@ -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); } } }
services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java +9 −3 Original line number Diff line number Diff line Loading @@ -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(); } Loading