Merge "Add ConnectionService for Telecom to bind to" into udc-dev
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f6c9fab..e216f88 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8217,6 +8217,14 @@
             </intent-filter>
         </service>
 
+        <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
+                 android:permission="android.permission.BIND_CONNECTION_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
new file mode 100644
index 0000000..19d8b87
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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 android.content.ComponentName;
+import android.telecom.ConnectionService;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/** Service for Telecom to bind to when call metadata is synced between devices. */
+public class CallMetadataSyncConnectionService extends ConnectionService {
+
+    private TelecomManager mTelecomManager;
+    private final Map<String, PhoneAccountHandle> mPhoneAccountHandles = new HashMap<>();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mTelecomManager = getSystemService(TelecomManager.class);
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * 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));
+    }
+
+    @VisibleForTesting
+    PhoneAccount createPhoneAccount(String packageName, String humanReadableAppName) {
+        if (mPhoneAccountHandles.containsKey(packageName)) {
+            // Already exists!
+            return null;
+        }
+        final PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName(this, CallMetadataSyncConnectionService.class),
+                UUID.randomUUID().toString());
+        mPhoneAccountHandles.put(packageName, handle);
+        return new PhoneAccount.Builder(handle, humanReadableAppName)
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
+                        | PhoneAccount.CAPABILITY_SELF_MANAGED).build();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
new file mode 100644
index 0000000..3ed95eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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 android.platform.test.annotations.Presubmit;
+import android.telecom.PhoneAccount;
+import android.testing.AndroidTestingRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class CallMetadataSyncConnectionServiceTest {
+
+    private CallMetadataSyncConnectionService mSyncConnectionService;
+
+    @Before
+    public void setUp() throws Exception {
+        mSyncConnectionService = new CallMetadataSyncConnectionService() {
+            @Override
+            public String getPackageName() {
+                return "android";
+            }
+        };
+    }
+
+    @Test
+    public void createPhoneAccount_success() {
+        final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
+                "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");
+        final PhoneAccount phoneAccount2 = mSyncConnectionService.createPhoneAccount(
+                "com.google.test", "Test App #2");
+        assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+        assertWithMessage("Unexpectedly created second phone account").that(phoneAccount2).isNull();
+    }
+}