Connection creation and service wiring for WiFi call managers (2/3)

Bug: 16469413
Change-Id: Id6d495c20aa57ffc961336d49693c1a7f91d4cb3
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index d76e314..d6373a1 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -28,6 +28,7 @@
 import android.telecomm.ConnectionRequest;
 import android.telecomm.GatewayInfo;
 import android.telecomm.ParcelableConnection;
+import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
 import android.telecomm.Response;
 import android.telecomm.StatusHints;
@@ -82,7 +83,8 @@
         void onCallerDisplayNameChanged(Call call);
         void onVideoStateChanged(Call call);
         void onStartActivityFromInCall(Call call, PendingIntent intent);
-        void onPhoneAccountChanged(Call call);
+        void onTargetPhoneAccountChanged(Call call);
+        void onConnectionManagerPhoneAccountChanged(Call call);
     }
 
     abstract static class ListenerBase implements Listener {
@@ -129,7 +131,9 @@
         @Override
         public void onStartActivityFromInCall(Call call, PendingIntent intent) {}
         @Override
-        public void onPhoneAccountChanged(Call call) {}
+        public void onTargetPhoneAccountChanged(Call call) {}
+        @Override
+        public void onConnectionManagerPhoneAccountChanged(Call call) {}
     }
 
     private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -177,7 +181,9 @@
      * service. */
     private final GatewayInfo mGatewayInfo;
 
-    private PhoneAccountHandle mPhoneAccountHandle;
+    private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
+
+    private PhoneAccountHandle mTargetPhoneAccountHandle;
 
     private final Handler mHandler = new Handler();
 
@@ -271,21 +277,27 @@
      *
      * @param handle The handle to dial.
      * @param gatewayInfo Gateway information to use for the call.
-     * @param accountHandle Account information to use for the call.
+     * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
+     *         This account must be one that was registered with the
+     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
+     * @param targetPhoneAccountHandle Account information to use for the call. This account must be
+     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
      * @param isIncoming True if this is an incoming call.
      */
     Call(
             ConnectionServiceRepository repository,
             Uri handle,
             GatewayInfo gatewayInfo,
-            PhoneAccountHandle accountHandle,
+            PhoneAccountHandle connectionManagerPhoneAccountHandle,
+            PhoneAccountHandle targetPhoneAccountHandle,
             boolean isIncoming,
             boolean isConference) {
         mState = isConference ? CallState.ACTIVE : CallState.NEW;
         mRepository = repository;
         setHandle(handle, CallPropertyPresentation.ALLOWED);
         mGatewayInfo = gatewayInfo;
-        mPhoneAccountHandle = accountHandle;
+        mConnectionManagerPhoneAccountHandle = connectionManagerPhoneAccountHandle;
+        mTargetPhoneAccountHandle = targetPhoneAccountHandle;
         mIsIncoming = isIncoming;
         mIsConference = isConference;
         maybeLoadCannedSmsResponses();
@@ -440,15 +452,29 @@
         return mGatewayInfo;
     }
 
-    PhoneAccountHandle getPhoneAccount() {
-        return mPhoneAccountHandle;
+    PhoneAccountHandle getConnectionManagerPhoneAccount() {
+        return mConnectionManagerPhoneAccountHandle;
     }
 
-    void setPhoneAccount(PhoneAccountHandle accountHandle) {
-        if (!Objects.equals(mPhoneAccountHandle, accountHandle)) {
-            mPhoneAccountHandle = accountHandle;
+    void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
+        if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
+            mConnectionManagerPhoneAccountHandle = accountHandle;
             for (Listener l : mListeners) {
-                l.onPhoneAccountChanged(this);
+                l.onConnectionManagerPhoneAccountChanged(this);
+            }
+        }
+
+    }
+
+    PhoneAccountHandle getTargetPhoneAccount() {
+        return mTargetPhoneAccountHandle;
+    }
+
+    void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
+        if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
+            mTargetPhoneAccountHandle = accountHandle;
+            for (Listener l : mListeners) {
+                l.onTargetPhoneAccountChanged(this);
             }
         }
     }
@@ -573,7 +599,7 @@
             ConnectionRequest request, ParcelableConnection connection) {
         mCreateConnectionProcessor = null;
         setState(getStateFromConnectionState(connection.getState()));
-        setPhoneAccount(connection.getPhoneAccount());
+        setTargetPhoneAccount(connection.getPhoneAccount());
         setHandle(connection.getHandle(), connection.getHandlePresentation());
         setCallerDisplayName(
                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index 1bb3c12..33c6d5a 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -120,7 +120,7 @@
         Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));
 
         final int presentation = getPresentation(call);
-        final PhoneAccountHandle accountHandle = call.getPhoneAccount();
+        final PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
 
         // TODO(vt): Once data usage is available, wire it up here.
         int callFeatures = getCallFeatures(call.getVideoStateHistory());
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index fc5a71a..5ce9fe8 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -270,6 +270,7 @@
                 mConnectionServiceRepository,
                 null /* handle */,
                 null /* gatewayInfo */,
+                null /* connectionManagerPhoneAccount */,
                 phoneAccountHandle,
                 true /* isIncoming */,
                 false /* isConference */);
@@ -316,6 +317,7 @@
                 mConnectionServiceRepository,
                 uriHandle,
                 gatewayInfo,
+                null /* connectionManagerPhoneAccount */,
                 accountHandle,
                 false /* isIncoming */,
                 false /* isConference */);
@@ -330,20 +332,20 @@
         final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
         if (emergencyCall) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
-            call.setPhoneAccount(null);
+            call.setTargetPhoneAccount(null);
         } else if (accountHandle != null) {
             Log.d(this, "CALL with phone account: " + accountHandle);
-            call.setPhoneAccount(accountHandle);
+            call.setTargetPhoneAccount(accountHandle);
         } else {
             // No preset account, check if default exists
             PhoneAccountHandle defaultAccountHandle =
                     app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
             if (defaultAccountHandle != null) {
-                call.setPhoneAccount(defaultAccountHandle);
+                call.setTargetPhoneAccount(defaultAccountHandle);
             }
         }
 
-        if (call.getPhoneAccount() != null || emergencyCall) {
+        if (call.getTargetPhoneAccount() != null || emergencyCall) {
             // If the account is selected, proceed to place the outgoing call
             call.startCreateConnection();
         } else {
@@ -362,7 +364,8 @@
                 mConnectionServiceRepository,
                 null /* handle */,
                 null /* gatewayInfo */,
-                null /* phoneAccount */,
+                null /* connectionManagerPhoneAccount */,
+                null /* targetPhoneAccount */,
                 false /* isIncoming */,
                 true /* isConference */);
         conferenceCall.addListener(this);
@@ -539,7 +542,7 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Attemped to add account to unknown call %s", call);
         } else {
-            call.setPhoneAccount(account);
+            call.setTargetPhoneAccount(account);
             call.startCreateConnection();
         }
     }
diff --git a/src/com/android/telecomm/ConnectionServiceRepository.java b/src/com/android/telecomm/ConnectionServiceRepository.java
index cb8d95d..ec2c90e 100644
--- a/src/com/android/telecomm/ConnectionServiceRepository.java
+++ b/src/com/android/telecomm/ConnectionServiceRepository.java
@@ -56,7 +56,10 @@
     ConnectionServiceWrapper getService(ComponentName componentName) {
         ConnectionServiceWrapper service = mServiceCache.get(componentName);
         if (service == null) {
-            service = new ConnectionServiceWrapper(componentName, this);
+            service = new ConnectionServiceWrapper(
+                    componentName,
+                    this,
+                    TelecommApp.getInstance().getPhoneAccountRegistrar());
             service.addListener(this);
             mServiceCache.put(componentName, service);
         }
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index 4238d61..c791932 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -29,6 +29,8 @@
 import android.telecomm.ConnectionService;
 import android.telecomm.GatewayInfo;
 import android.telecomm.ParcelableConnection;
+import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountHandle;
 import android.telecomm.StatusHints;
 import android.telephony.DisconnectCause;
 
@@ -39,8 +41,10 @@
 import com.android.internal.telecomm.RemoteServiceCallback;
 import com.google.common.base.Preconditions;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -534,11 +538,18 @@
      *
      * @param componentName The component name of the service with which to bind.
      * @param connectionServiceRepository Connection service repository.
+     * @param phoneAccountRegistrar Phone account registrar
      */
     ConnectionServiceWrapper(
-            ComponentName componentName, ConnectionServiceRepository connectionServiceRepository) {
+            ComponentName componentName,
+            ConnectionServiceRepository connectionServiceRepository,
+            PhoneAccountRegistrar phoneAccountRegistrar) {
         super(ConnectionService.SERVICE_INTERFACE, componentName);
         mConnectionServiceRepository = connectionServiceRepository;
+        phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
+            // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
+            // To do this, we must proxy remote ConnectionService objects
+        });
     }
 
     /** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -575,16 +586,18 @@
                             NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_ORIGINAL_URI,
                             gatewayInfo.getOriginalHandle());
                 }
-                ConnectionRequest request = new ConnectionRequest(
-                        call.getPhoneAccount(),
-                        callId,
-                        call.getHandle(),
-                        call.getHandlePresentation(),
-                        extras,
-                        call.getVideoState());
 
                 try {
-                    mServiceInterface.createConnection(request, call.isIncoming());
+                    mServiceInterface.createConnection(
+                            call.getConnectionManagerPhoneAccount(),
+                            new ConnectionRequest(
+                                    call.getTargetPhoneAccount(),
+                                    callId,
+                                    call.getHandle(),
+                                    call.getHandlePresentation(),
+                                    extras,
+                                    call.getVideoState()),
+                            call.isIncoming());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                     mPendingResponses.remove(callId).handleCreateConnectionFailed(
@@ -665,7 +678,7 @@
         }
     }
 
-    /** @see ConnectionService#answer(String) */
+    /** @see ConnectionService#answer(String,int) */
     void answer(Call call, int videoState) {
         if (isServiceValid("answer")) {
             try {
@@ -842,43 +855,86 @@
     }
 
     private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
-        final List<IBinder> connectionServices = new ArrayList<>();
-        final List<ComponentName> components = new ArrayList<>();
-        final List<ConnectionServiceWrapper> servicesAttempted = new ArrayList<>();
-        final Collection<ConnectionServiceWrapper> services =
-                mConnectionServiceRepository.lookupServices();
+        PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
 
-        Log.v(this, "queryRemoteConnectionServices, services: " + services.size());
+        // Only give remote connection services to this connection service if it is listed as
+        // the connection manager.
+        PhoneAccountHandle simCallManager = registrar.getSimCallManager();
+        if (simCallManager == null ||
+                !simCallManager.getComponentName().equals(getComponentName())) {
+            noRemoteServices(callback);
+            return;
+        }
 
-        for (ConnectionServiceWrapper cs : services) {
-            if (cs != this) {
-                final ConnectionServiceWrapper currentConnectionService = cs;
-                cs.mBinder.bind(new BindCallback() {
-                    @Override
-                    public void onSuccess() {
-                        Log.d(this, "Adding ***** %s", currentConnectionService.getComponentName());
-                        connectionServices.add(
-                                currentConnectionService.mServiceInterface.asBinder());
-                        components.add(currentConnectionService.getComponentName());
-                        maybeComplete();
-                    }
-
-                    @Override
-                    public void onFailure() {
-                        maybeComplete();
-                    }
-
-                    private void maybeComplete() {
-                        servicesAttempted.add(currentConnectionService);
-                        if (servicesAttempted.size() == services.size() - 1) {
-                            try {
-                                callback.onResult(components, connectionServices);
-                            } catch (RemoteException ignored) {
-                            }
-                        }
-                    }
-                });
+        // Make a list of ConnectionServices that are listed as being associated with SIM accounts
+        final Set<ConnectionServiceWrapper> simServices = new HashSet<>();
+        for (PhoneAccountHandle handle : registrar.getEnabledPhoneAccounts()) {
+            PhoneAccount account = registrar.getPhoneAccount(handle);
+            if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
+                ConnectionServiceWrapper service =
+                        mConnectionServiceRepository.getService(handle.getComponentName());
+                if (service != null) {
+                    simServices.add(service);
+                }
             }
         }
+
+        final List<ComponentName> simServiceComponentNames = new ArrayList<>();
+        final List<IBinder> simServiceBinders = new ArrayList<>();
+
+        Log.v(this, "queryRemoteConnectionServices, simServices = %s", simServices);
+
+        for (ConnectionServiceWrapper simService : simServices) {
+            if (simService == this) {
+                // Only happens in the unlikely case that a SIM service is also a SIM call manager
+                continue;
+            }
+
+            final ConnectionServiceWrapper currentSimService = simService;
+
+            currentSimService.mBinder.bind(new BindCallback() {
+                @Override
+                public void onSuccess() {
+                    Log.d(this, "Adding simService %s", currentSimService.getComponentName());
+                    simServiceComponentNames.add(currentSimService.getComponentName());
+                    simServiceBinders.add(currentSimService.mServiceInterface.asBinder());
+                    maybeComplete();
+                }
+
+                @Override
+                public void onFailure() {
+                    Log.d(this, "Failed simService %s", currentSimService.getComponentName());
+                    // We know maybeComplete() will always be a no-op from now on, so go ahead and
+                    // signal failure of the entire request
+                    noRemoteServices(callback);
+                }
+
+                private void maybeComplete() {
+                    if (simServiceComponentNames.size() == simServices.size()) {
+                        setRemoteServices(callback, simServiceComponentNames, simServiceBinders);
+                    }
+                }
+            });
+        }
+    }
+
+    private void setRemoteServices(
+            RemoteServiceCallback callback,
+            List<ComponentName> componentNames,
+            List<IBinder> binders) {
+        try {
+            callback.onResult(componentNames, binders);
+        } catch (RemoteException e) {
+            Log.e(this, e, "Contacting ConnectionService %s",
+                    ConnectionServiceWrapper.this.getComponentName());
+        }
+    }
+
+    private void noRemoteServices(RemoteServiceCallback callback) {
+        try {
+            callback.onResult(Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        } catch (RemoteException e) {
+            Log.e(this, e, "Contacting ConnectionService %s", this.getComponentName());
+        }
     }
 }
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index 0f28974..f3449ff 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -39,23 +39,23 @@
     private static class CallAttemptRecord {
         // The PhoneAccount describing the target connection service which we will
         // contact in order to process an attempt
-        public final PhoneAccountHandle targetConnectionServiceAccount;
+        public final PhoneAccountHandle connectionManagerPhoneAccount;
         // The PhoneAccount which we will tell the target connection service to use
         // for attempting to make the actual phone call
-        public final PhoneAccountHandle phoneAccount;
+        public final PhoneAccountHandle targetPhoneAccount;
 
         public CallAttemptRecord(
-                PhoneAccountHandle targetConnectionServiceAccount,
-                PhoneAccountHandle phoneAccount) {
-            this.targetConnectionServiceAccount = targetConnectionServiceAccount;
-            this.phoneAccount = phoneAccount;
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                PhoneAccountHandle targetPhoneAccount) {
+            this.connectionManagerPhoneAccount = connectionManagerPhoneAccount;
+            this.targetPhoneAccount = targetPhoneAccount;
         }
 
         @Override
         public String toString() {
             return "CallAttemptRecord("
-                    + Objects.toString(targetConnectionServiceAccount) + ","
-                    + Objects.toString(phoneAccount) + ")";
+                    + Objects.toString(connectionManagerPhoneAccount) + ","
+                    + Objects.toString(targetPhoneAccount) + ")";
         }
     }
 
@@ -77,9 +77,9 @@
     void process() {
         Log.v(this, "process");
         mAttemptRecords = new ArrayList<>();
-        if (mCall.getPhoneAccount() != null) {
+        if (mCall.getTargetPhoneAccount() != null) {
             mAttemptRecords.add(
-                    new CallAttemptRecord(mCall.getPhoneAccount(), mCall.getPhoneAccount()));
+                    new CallAttemptRecord(mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
         }
         adjustAttemptsForWifi();
         adjustAttemptsForEmergency();
@@ -112,12 +112,14 @@
             CallAttemptRecord attempt = mAttemptRecordIterator.next();
             Log.i(this, "Trying attempt %s", attempt);
             ConnectionServiceWrapper service =
-                    mRepository.getService(attempt.targetConnectionServiceAccount.getComponentName());
+                    mRepository.getService(
+                            attempt.connectionManagerPhoneAccount.getComponentName());
             if (service == null) {
                 Log.i(this, "Found no connection service for attempt %s", attempt);
                 attemptNextPhoneAccount();
             } else {
-                mCall.setPhoneAccount(attempt.phoneAccount);
+                mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
+                mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
                 mCall.setConnectionService(service);
                 Log.i(this, "Attempting to call from %s", service.getComponentName());
                 service.createConnection(mCall, new Response(service));
@@ -146,12 +148,12 @@
         PhoneAccountHandle simCallManager =
                 TelecommApp.getInstance().getPhoneAccountRegistrar().getSimCallManager();
         if (simCallManager != null &&
-                !Objects.equals(simCallManager, mAttemptRecords.get(0).phoneAccount)) {
+                !Objects.equals(simCallManager, mAttemptRecords.get(0).targetPhoneAccount)) {
             mAttemptRecords.set(
                     0,
                     new CallAttemptRecord(
                             simCallManager,
-                            mAttemptRecords.get(0).phoneAccount));
+                            mAttemptRecords.get(0).targetPhoneAccount));
         }
     }
 
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 10ea443..e12372d 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -109,7 +109,7 @@
         }
 
         @Override
-        public void onPhoneAccountChanged(Call call) {
+        public void onTargetPhoneAccountChanged(Call call) {
             updateCall(call);
         }
     };
@@ -349,7 +349,7 @@
                 callerDisplayName,
                 call.getCallerDisplayNamePresentation(),
                 call.getGatewayInfo(),
-                call.getPhoneAccount(),
+                call.getTargetPhoneAccount(),
                 call.getVideoCallProvider(),
                 parentCallId,
                 childCallIds,
diff --git a/src/com/android/telecomm/MissedCallNotifier.java b/src/com/android/telecomm/MissedCallNotifier.java
index ed79f5a..e9e453b 100644
--- a/src/com/android/telecomm/MissedCallNotifier.java
+++ b/src/com/android/telecomm/MissedCallNotifier.java
@@ -279,7 +279,7 @@
                         }
 
                         // Convert the data to a call object
-                        Call call = new Call(null, null, null, null, true, false);
+                        Call call = new Call(null, null, null, null, null, true, false);
                         call.setDisconnectCause(DisconnectCause.INCOMING_MISSED, "");
                         call.setState(CallState.DISCONNECTED);
 
diff --git a/src/com/android/telecomm/PhoneAccountPreferencesActivity.java b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
index 2430f26..2551b47 100644
--- a/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
+++ b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
@@ -89,7 +89,7 @@
             List<PhoneAccountHandle> allAccounts = registrar.getAllPhoneAccountHandles();
             for (int i = 0; i < allAccounts.size(); i++) {
                 PhoneAccount account = registrar.getPhoneAccount(allAccounts.get(i));
-                if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_CALL_MANAGER) != 0) {
+                if ((account.getCapabilities() & PhoneAccount.CAPABILITY_CONNECTION_MANAGER) != 0) {
                     simCallManagers.add(allAccounts.get(i));
                 }
             }
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index f236f58..f6e522d 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -16,23 +16,36 @@
 
 package com.android.telecomm;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
 
 import android.content.ComponentName;
 import android.content.Context;
 
-import android.content.SharedPreferences;
 import android.net.Uri;
 import android.telecomm.TelecommManager;
+import android.util.AtomicFile;
+import android.util.Xml;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
@@ -40,20 +53,31 @@
  * {@link TelecommServiceImpl}, with the notable exception that {@link TelecommServiceImpl} is
  * responsible for security checking to make sure that the caller has proper authority over
  * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
- *
- * TODO(santoscordon): Replace this implementation with a proper database stored in a Telecomm
- * provider.
  */
-final class PhoneAccountRegistrar {
-    private static final String TELECOMM_PREFERENCES = "telecomm_prefs";
-    private static final String PREFERENCE_PHONE_ACCOUNTS = "phone_accounts";
+public final class PhoneAccountRegistrar {
 
-    private final Context mContext;
-    private final State mState;
+    public abstract static class Listener {
+        public void onAccountsChanged(PhoneAccountRegistrar registrar) {}
+        public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {}
+        public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
+    }
+
+    private static final String FILE_NAME = "phone-account-registrar-state.xml";
+
+    private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
+    private final AtomicFile mAtomicFile;
+    private State mState;
 
     PhoneAccountRegistrar(Context context) {
-        mContext = context;
-        mState = readState();
+        this(context, FILE_NAME);
+    }
+
+    @VisibleForTesting
+    public PhoneAccountRegistrar(Context context, String fileName) {
+        // TODO: Change file location when Telecomm is part of system
+        mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
+        mState = new State();
+        read();
     }
 
     public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
@@ -97,7 +121,13 @@
             }
 
             if (!found) {
-                Log.w(this, "Trying to set nonexistent default outgoing phone accountHandle %s",
+                Log.w(this, "Trying to set nonexistent default outgoing %s",
+                        accountHandle);
+                return;
+            }
+
+            if (!has(getPhoneAccount(accountHandle), PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
+                Log.w(this, "Trying to set non-call-provider default outgoing %s",
                         accountHandle);
                 return;
             }
@@ -106,6 +136,7 @@
         }
 
         write();
+        fireDefaultOutgoingChanged();
     }
 
     public void setSimCallManager(PhoneAccountHandle callManager) {
@@ -114,17 +145,27 @@
             if (callManagerAccount == null) {
                 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
                 return;
-            } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_SIM_CALL_MANAGER)) {
+            } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
                 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
                 return;
             }
         }
         mState.simCallManager = callManager;
         write();
+        fireSimCallManagerChanged();
     }
 
     public PhoneAccountHandle getSimCallManager() {
-        return mState.simCallManager;
+        if (mState.simCallManager != null) {
+            // Return the registered sim call manager iff it still exists (we keep a sticky
+            // setting to survive account deletion and re-addition)
+            for (int i = 0; i < mState.accounts.size(); i++) {
+                if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) {
+                    return mState.simCallManager;
+                }
+            }
+        }
+        return null;
     }
 
     public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
@@ -169,6 +210,7 @@
         }
 
         write();
+        fireAccountsChanged();
     }
 
     // STOPSHIP: Hack to edit the account registered by Babel so it shows up properly
@@ -179,11 +221,10 @@
                         account.getAccountHandle(),
                         account.getHandle(),
                         account.getSubscriptionNumber(),
-                        PhoneAccount.CAPABILITY_SIM_CALL_MANAGER,
+                        PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
                         account.getIconResId(),
                         account.getLabel(),
-                        account.getShortDescription(),
-                        account.isVideoCallingSupported())
+                        account.getShortDescription())
                 : account;
     }
 
@@ -196,6 +237,7 @@
         }
 
         write();
+        fireAccountsChanged();
     }
 
     public void clearAccounts(String packageName) {
@@ -209,6 +251,33 @@
         }
 
         write();
+        fireAccountsChanged();
+    }
+
+    public void addListener(Listener l) {
+        mListeners.add(l);
+    }
+
+    public void removeListener(Listener l) {
+        mListeners.remove(l);
+    }
+
+    private void fireAccountsChanged() {
+        for (Listener l : mListeners) {
+            l.onAccountsChanged(this);
+        }
+    }
+
+    private void fireDefaultOutgoingChanged() {
+        for (Listener l : mListeners) {
+            l.onDefaultOutgoingChanged(this);
+        }
+    }
+
+    private void fireSimCallManagerChanged() {
+        for (Listener l : mListeners) {
+            l.onSimCallManagerChanged(this);
+        }
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -232,7 +301,8 @@
     /**
      * The state of this {@code PhoneAccountRegistrar}.
      */
-    private static class State {
+    @VisibleForTesting
+    public static class State {
         /**
          * The account selected by the user to be employed by default for making outgoing calls.
          * If the user has not made such a selection, then this is null.
@@ -240,7 +310,7 @@
         public PhoneAccountHandle defaultOutgoing = null;
 
         /**
-         * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_SIM_CALL_MANAGER} which
+         * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which
          * manages and optimizes a user's PSTN SIM connections.
          */
         public PhoneAccountHandle simCallManager;
@@ -257,176 +327,296 @@
     //
 
     private void write() {
-        writeState(mState);
-    }
-
-    private State readState() {
+        final FileOutputStream os;
         try {
-            String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
-            Log.v(this, "read() obtained serialized state: %s", serialized);
-            State state = serialized == null
-                    ? new State()
-                    : deserializeState(serialized);
-            Log.v(this, "read() obtained state: %s", state);
-            return state;
-        } catch (JSONException e) {
-            Log.e(this, e, "read");
-            return new State();
+            os = mAtomicFile.startWrite();
+            boolean success = false;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+                writeToXml(mState, serializer);
+                serializer.flush();
+                success = true;
+            } finally {
+                if (success) {
+                    mAtomicFile.finishWrite(os);
+                } else {
+                    mAtomicFile.failWrite(os);
+                }
+            }
+        } catch (IOException e) {
+            Log.e(this, e, "Writing state to XML file");
         }
     }
 
-    private boolean writeState(State state) {
+    private void read() {
+        final InputStream is;
         try {
-            Log.v(this, "write() writing state: %s", state);
-            String serialized = serializeState(state);
-            Log.v(this, "write() writing serialized state: %s", serialized);
-            boolean success = getPreferences()
-                    .edit()
-                    .putString(PREFERENCE_PHONE_ACCOUNTS, serialized)
-                    .commit();
-            Log.v(this, "serialized state was written with success = %b", success);
-            return success;
-        } catch (JSONException e) {
-            Log.e(this, e, "write");
-            return false;
+            is = mAtomicFile.openRead();
+        } catch (FileNotFoundException ex) {
+            return;
+        }
+
+        XmlPullParser parser;
+        try {
+            parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(is), null);
+            parser.nextTag();
+            mState = readFromXml(parser);
+        } catch (IOException | XmlPullParserException e) {
+            Log.e(this, e, "Reading state from XML file");
+            mState = new State();
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+                Log.e(this, e, "Closing InputStream");
+            }
         }
     }
 
-    private SharedPreferences getPreferences() {
-        return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
+    private static void writeToXml(State state, XmlSerializer serializer)
+            throws IOException {
+        sStateXml.writeToXml(state, serializer);
     }
 
-    private String serializeState(State s) throws JSONException {
-        // TODO: If this is used in production, remove the indent (=> do not pretty print)
-        return sStateJson.toJson(s).toString(2);
-    }
-
-    private State deserializeState(String s) throws JSONException {
-        return sStateJson.fromJson(new JSONObject(new JSONTokener(s)));
+    private static State readFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        State s = sStateXml.readFromXml(parser);
+        return s != null ? s : new State();
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     //
-    // JSON serialization
+    // XML serialization
     //
 
-    private interface Json<T> {
-        JSONObject toJson(T o) throws JSONException;
-        T fromJson(JSONObject json) throws JSONException;
+    @VisibleForTesting
+    public interface XmlSerialization<T> {
+        /**
+         * Write the supplied object to XML
+         */
+        void writeToXml(T o, XmlSerializer serializer) throws IOException;
+
+        /**
+         * Read from the supplied XML into a new object, returning null in case of an
+         * unrecoverable schema mismatch or other data error. 'parser' must be already
+         * positioned at the first tag that is expected to have been emitted by this
+         * object's writeToXml(). This object tries to fail early without modifying
+         * 'parser' if it does not recognize the data it sees.
+         */
+        T readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
     }
 
-    private static final Json<State> sStateJson =
-            new Json<State>() {
+    @VisibleForTesting
+    public static final XmlSerialization<State> sStateXml =
+            new XmlSerialization<State>() {
+        private static final String CLASS_STATE = "phone_account_registrar_state";
         private static final String DEFAULT_OUTGOING = "default_outgoing";
         private static final String SIM_CALL_MANAGER = "sim_call_manager";
         private static final String ACCOUNTS = "accounts";
 
         @Override
-        public JSONObject toJson(State o) throws JSONException {
-            JSONObject json = new JSONObject();
+        public void writeToXml(State o, XmlSerializer serializer)
+                throws IOException {
+            serializer.startTag(null, CLASS_STATE);
+
             if (o.defaultOutgoing != null) {
-                json.put(DEFAULT_OUTGOING, sPhoneAccountHandleJson.toJson(o.defaultOutgoing));
+                serializer.startTag(null, DEFAULT_OUTGOING);
+                sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
+                serializer.endTag(null, DEFAULT_OUTGOING);
             }
+
             if (o.simCallManager != null) {
-                json.put(SIM_CALL_MANAGER, sPhoneAccountHandleJson.toJson(o.simCallManager));
+                serializer.startTag(null, SIM_CALL_MANAGER);
+                sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
+                serializer.endTag(null, SIM_CALL_MANAGER);
             }
-            JSONArray accounts = new JSONArray();
+
+            serializer.startTag(null, ACCOUNTS);
             for (PhoneAccount m : o.accounts) {
-                accounts.put(sPhoneAccountJson.toJson(m));
+                sPhoneAccountXml.writeToXml(m, serializer);
             }
-            json.put(ACCOUNTS, accounts);
-            return json;
+            serializer.endTag(null, ACCOUNTS);
+
+            serializer.endTag(null, CLASS_STATE);
         }
 
         @Override
-        public State fromJson(JSONObject json) throws JSONException {
-            State s = new State();
-            if (json.has(DEFAULT_OUTGOING)) {
-                try {
-                    s.defaultOutgoing = sPhoneAccountHandleJson.fromJson(
-                            (JSONObject) json.get(DEFAULT_OUTGOING));
-                } catch (Exception e) {
-                    Log.e(this, e, "Extracting PhoneAccountHandle");
-                }
-            }
-            if (json.has(SIM_CALL_MANAGER)) {
-                try {
-                    s.simCallManager = sPhoneAccountHandleJson.fromJson(
-                            (JSONObject) json.get(SIM_CALL_MANAGER));
-                } catch (Exception e) {
-                    Log.e(this, e, "Extracting PhoneAccountHandle");
-                }
-            }
-            if (json.has(ACCOUNTS)) {
-                JSONArray accounts = (JSONArray) json.get(ACCOUNTS);
-                for (int i = 0; i < accounts.length(); i++) {
-                    try {
-                        s.accounts.add(sPhoneAccountJson.fromJson(
-                                (JSONObject) accounts.get(i)));
-                    } catch (Exception e) {
-                        Log.e(this, e, "Extracting phone account");
+        public State readFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            if (parser.getName().equals(CLASS_STATE)) {
+                State s = new State();
+                int outerDepth = parser.getDepth();
+                while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                    if (parser.getName().equals(DEFAULT_OUTGOING)) {
+                        parser.nextTag();
+                        s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser);
+                    } else if (parser.getName().equals(SIM_CALL_MANAGER)) {
+                        parser.nextTag();
+                        s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser);
+                    } else if (parser.getName().equals(ACCOUNTS)) {
+                        int accountsDepth = parser.getDepth();
+                        while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
+                            PhoneAccount account = sPhoneAccountXml.readFromXml(parser);
+                            if (account != null) {
+                                s.accounts.add(account);
+                            }
+                        }
                     }
                 }
+                return s;
             }
-            return s;
+            return null;
         }
     };
 
-    private static final Json<PhoneAccount> sPhoneAccountJson =
-            new Json<PhoneAccount>() {
-        private static final String ACCOUNT = "account";
+    @VisibleForTesting
+    public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
+            new XmlSerialization<PhoneAccount>() {
+        private static final String CLASS_PHONE_ACCOUNT = "phone_account";
+        private static final String ACCOUNT_HANDLE = "account_handle";
         private static final String HANDLE = "handle";
         private static final String SUBSCRIPTION_NUMBER = "subscription_number";
         private static final String CAPABILITIES = "capabilities";
         private static final String ICON_RES_ID = "icon_res_id";
         private static final String LABEL = "label";
         private static final String SHORT_DESCRIPTION = "short_description";
-        private static final String VIDEO_CALLING_SUPPORTED = "video_calling_supported";
 
         @Override
-        public JSONObject toJson(PhoneAccount o) throws JSONException {
-            return new JSONObject()
-                    .put(ACCOUNT, sPhoneAccountHandleJson.toJson(o.getAccountHandle()))
-                    .put(HANDLE, o.getHandle().toString())
-                    .put(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber())
-                    .put(CAPABILITIES, o.getCapabilities())
-                    .put(ICON_RES_ID, o.getIconResId())
-                    .put(LABEL, o.getLabel())
-                    .put(SHORT_DESCRIPTION, o.getShortDescription())
-                    .put(VIDEO_CALLING_SUPPORTED, (Boolean) o.isVideoCallingSupported());
+        public void writeToXml(PhoneAccount o, XmlSerializer serializer)
+                throws IOException {
+            serializer.startTag(null, CLASS_PHONE_ACCOUNT);
+
+            serializer.startTag(null, ACCOUNT_HANDLE);
+            sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
+            serializer.endTag(null, ACCOUNT_HANDLE);
+
+            serializer.startTag(null, HANDLE);
+            serializer.text(o.getHandle().toString());
+            serializer.endTag(null, HANDLE);
+
+            serializer.startTag(null, SUBSCRIPTION_NUMBER);
+            serializer.text(o.getSubscriptionNumber());
+            serializer.endTag(null, SUBSCRIPTION_NUMBER);
+
+            serializer.startTag(null, CAPABILITIES);
+            serializer.text(Integer.toString(o.getCapabilities()));
+            serializer.endTag(null, CAPABILITIES);
+
+            serializer.startTag(null, ICON_RES_ID);
+            serializer.text(Integer.toString(o.getIconResId()));
+            serializer.endTag(null, ICON_RES_ID);
+
+            serializer.startTag(null, LABEL);
+            serializer.text(Objects.toString(o.getLabel()));
+            serializer.endTag(null, LABEL);
+
+            serializer.startTag(null, SHORT_DESCRIPTION);
+            serializer.text(Objects.toString(o.getShortDescription()));
+            serializer.endTag(null, SHORT_DESCRIPTION);
+
+            serializer.endTag(null, CLASS_PHONE_ACCOUNT);
         }
 
         @Override
-        public PhoneAccount fromJson(JSONObject json) throws JSONException {
-            return new PhoneAccount(
-                    sPhoneAccountHandleJson.fromJson((JSONObject) json.get(ACCOUNT)),
-                    Uri.parse((String) json.get(HANDLE)),
-                    (String) json.get(SUBSCRIPTION_NUMBER),
-                    (int) json.get(CAPABILITIES),
-                    (int) json.get(ICON_RES_ID),
-                    (String) json.get(LABEL),
-                    (String) json.get(SHORT_DESCRIPTION),
-                    (Boolean) json.get(VIDEO_CALLING_SUPPORTED));
+        public PhoneAccount readFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
+                int outerDepth = parser.getDepth();
+                PhoneAccountHandle accountHandle = null;
+                Uri handle = null;
+                String subscriptionNumber = null;
+                int capabilities = 0;
+                int iconResId = 0;
+                String label = null;
+                String shortDescription = null;
+
+                while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                    if (parser.getName().equals(ACCOUNT_HANDLE)) {
+                        parser.nextTag();
+                        accountHandle = sPhoneAccountHandleXml.readFromXml(parser);
+                    } else if (parser.getName().equals(HANDLE)) {
+                        parser.next();
+                        handle = Uri.parse(parser.getText());
+                    } else if (parser.getName().equals(SUBSCRIPTION_NUMBER)) {
+                        parser.next();
+                        subscriptionNumber = parser.getText();
+                    } else if (parser.getName().equals(CAPABILITIES)) {
+                        parser.next();
+                        capabilities = Integer.parseInt(parser.getText());
+                    } else if (parser.getName().equals(ICON_RES_ID)) {
+                        parser.next();
+                        iconResId = Integer.parseInt(parser.getText());
+                    } else if (parser.getName().equals(LABEL)) {
+                        parser.next();
+                        label = parser.getText();
+                    } else if (parser.getName().equals(SHORT_DESCRIPTION)) {
+                        parser.next();
+                        shortDescription = parser.getText();
+                    }
+                }
+                if (accountHandle != null) {
+                    return new PhoneAccount(
+                            accountHandle,
+                            handle,
+                            subscriptionNumber,
+                            capabilities,
+                            iconResId,
+                            label,
+                            shortDescription);
+                }
+            }
+            return null;
         }
     };
 
-    private static final Json<PhoneAccountHandle> sPhoneAccountHandleJson =
-            new Json<PhoneAccountHandle>() {
+    @VisibleForTesting
+    public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml =
+            new XmlSerialization<PhoneAccountHandle>() {
+        private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
         private static final String COMPONENT_NAME = "component_name";
         private static final String ID = "id";
 
         @Override
-        public JSONObject toJson(PhoneAccountHandle o) throws JSONException {
-            return new JSONObject()
-                    .put(COMPONENT_NAME, o.getComponentName().flattenToString())
-                    .put(ID, o.getId());
+        public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
+                throws IOException {
+            serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
+
+            serializer.startTag(null, COMPONENT_NAME);
+            serializer.text(o.getComponentName().flattenToString());
+            serializer.endTag(null, COMPONENT_NAME);
+
+            serializer.startTag(null, ID);
+            serializer.text(o.getId());
+            serializer.endTag(null, ID);
+
+            serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
         }
 
         @Override
-        public PhoneAccountHandle fromJson(JSONObject json) throws JSONException {
-            return new PhoneAccountHandle(
-                    ComponentName.unflattenFromString((String) json.get(COMPONENT_NAME)),
-                    (String) json.get(ID));
+        public PhoneAccountHandle readFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
+                String componentNameString = null;
+                String idString = null;
+                int outerDepth = parser.getDepth();
+                while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                    if (parser.getName().equals(COMPONENT_NAME)) {
+                        parser.next();
+                        componentNameString = parser.getText();
+                    } else if (parser.getName().equals(ID)) {
+                        parser.next();
+                        idString = parser.getText();
+                    }
+                }
+                if (componentNameString != null && idString != null) {
+                    return new PhoneAccountHandle(
+                            ComponentName.unflattenFromString(componentNameString),
+                            idString);
+                }
+            }
+            return null;
         }
     };
 }
diff --git a/src/com/android/telecomm/TelecommApp.java b/src/com/android/telecomm/TelecommApp.java
index cf7b8df..0f4f776 100644
--- a/src/com/android/telecomm/TelecommApp.java
+++ b/src/com/android/telecomm/TelecommApp.java
@@ -87,8 +87,7 @@
                 PhoneAccount.CAPABILITY_CALL_PROVIDER,
                 R.drawable.stat_sys_phone_call,
                 "Wi-Fi calling",
-                "Wi-Fi calling by Google Hangouts",
-                false);
+                "Wi-Fi calling by Google Hangouts");
         mPhoneAccountRegistrar.clearAccounts(
                 hangouts.getAccountHandle().getComponentName().getPackageName());
         mPhoneAccountRegistrar.registerPhoneAccount(hangouts);
diff --git a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
index dfce7c7..0dbb0b1 100644
--- a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
+++ b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
@@ -97,8 +97,7 @@
                 PhoneAccount.CAPABILITY_CALL_PROVIDER,
                 0,  // iconResId
                 "Dummy Service",
-                "a short description for the dummy service",
-                false);
+                "a short description for the dummy service");
         TelecommManager telecommManager =
                 (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
         telecommManager.registerPhoneAccount(account);
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionService.java b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
index 11febe5..30cccc0 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionService.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
@@ -316,9 +316,9 @@
                         mOriginalRequest.getVideoState());
                 RemoteConnection remoteConnection;
                 if (mIsIncoming) {
-                    remoteConnection = createRemoteIncomingConnection(connectionRequest);
+                    remoteConnection = createRemoteIncomingConnection(null, connectionRequest);
                 } else {
-                    remoteConnection = createRemoteOutgoingConnection(connectionRequest);
+                    remoteConnection = createRemoteOutgoingConnection(null, connectionRequest);
                 }
                 if (remoteConnection != null) {
                     remoteConnection.addListener(mRemoteListener);
@@ -380,7 +380,9 @@
 
     /** ${inheritDoc} */
     @Override
-    public Connection onCreateOutgoingConnection(final ConnectionRequest originalRequest) {
+    public Connection onCreateOutgoingConnection(
+            PhoneAccountHandle connectionManagerAccount,
+            final ConnectionRequest originalRequest) {
 
         final Uri handle = originalRequest.getHandle();
         String number = originalRequest.getHandle().getSchemeSpecificPart();
@@ -426,7 +428,9 @@
 
     /** ${inheritDoc} */
     @Override
-    public Connection onCreateIncomingConnection(final ConnectionRequest request) {
+    public Connection onCreateIncomingConnection(
+            PhoneAccountHandle connectionManagerAccount,
+            final ConnectionRequest request) {
         PhoneAccountHandle accountHandle = request.getAccountHandle();
         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
 
diff --git a/tests/src/com/android/telecomm/tests/unit/ExampleTest.java b/tests/src/com/android/telecomm/tests/unit/ExampleTest.java
deleted file mode 100644
index 755267a..0000000
--- a/tests/src/com/android/telecomm/tests/unit/ExampleTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 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.telecomm.tests.unit;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.telecomm.CallActivity;
-
-/**
- * Unit tests for {@link CallActivity}.
- */
-@SmallTest
-public class ExampleTest extends AndroidTestCase {
-
-    /**
-     * TODO(santoscordon): Remove once there are real tests which can be used as proper examples.
-     */
-    @SmallTest
-    public void testExample1() throws Exception {
-        CallActivity callActivity = new CallActivity();
-
-        assertNotNull(callActivity);
-    }
-}
diff --git a/tests/src/com/android/telecomm/tests/unit/PhoneAccountRegistrarTest.java b/tests/src/com/android/telecomm/tests/unit/PhoneAccountRegistrarTest.java
new file mode 100644
index 0000000..c1493f1
--- /dev/null
+++ b/tests/src/com/android/telecomm/tests/unit/PhoneAccountRegistrarTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 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.telecomm.tests.unit;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.telecomm.Log;
+import com.android.telecomm.PhoneAccountRegistrar;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountHandle;
+import android.test.AndroidTestCase;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+public class PhoneAccountRegistrarTest extends AndroidTestCase {
+
+    private static final String FILE_NAME = "phone-account-registrar-test.xml";
+    private PhoneAccountRegistrar mRegistrar;
+
+    @Override
+    public void setUp() {
+        mRegistrar = new PhoneAccountRegistrar(getContext(), FILE_NAME);
+        mRegistrar.registerPhoneAccount(new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0"),
+                Uri.parse("tel:555-1212"),
+                "555-1212",
+                PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
+                0,
+                "label0",
+                "desc0"));
+        mRegistrar.registerPhoneAccount(new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1"),
+                Uri.parse("tel:555-1212"),
+                "555-1212",
+                PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION,
+                0,
+                "label1",
+                "desc1"));
+        mRegistrar.registerPhoneAccount(new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "id2"),
+                Uri.parse("tel:555-1212"),
+                "555-1212",
+                PhoneAccount.CAPABILITY_CALL_PROVIDER,
+                0,
+                "label2",
+                "desc2"));
+        mRegistrar.registerPhoneAccount(new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "id3"),
+                Uri.parse("tel:555-1212"),
+                "555-1212",
+                PhoneAccount.CAPABILITY_CALL_PROVIDER,
+                0,
+                "label2",
+                "desc2"));
+    }
+
+    @Override
+    public void tearDown() {
+        mRegistrar = null;
+        new File(getContext().getFilesDir(), FILE_NAME).delete();
+    }
+
+    private static <T> T roundTrip(
+            Object self,
+            T input,
+            PhoneAccountRegistrar.XmlSerialization<T> xml)
+            throws Exception {
+        Log.d(self, "Input = %s", input);
+
+        byte[] data;
+        {
+            XmlSerializer serializer = new FastXmlSerializer();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+            xml.writeToXml(input, serializer);
+            serializer.flush();
+            data = baos.toByteArray();
+        }
+
+        Log.d(self, "====== XML data ======\n%s", new String(data));
+
+        T result = null;
+        {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(data)), null);
+            parser.nextTag();
+            result = xml.readFromXml(parser);
+        }
+
+        Log.d(self, "result = " + result);
+
+        return result;
+    }
+
+    private void assertPhoneAccountHandleEquals(PhoneAccountHandle a, PhoneAccountHandle b) {
+        assertEquals(a.getComponentName().getPackageName(), b.getComponentName().getPackageName());
+        assertEquals(a.getComponentName().getClassName(), b.getComponentName().getClassName());
+        assertEquals(a.getId(), b.getId());
+    }
+
+    public void testPhoneAccountHandle() throws Exception {
+        PhoneAccountHandle input = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
+        PhoneAccountHandle result = roundTrip(this, input, PhoneAccountRegistrar.sPhoneAccountHandleXml);
+        assertPhoneAccountHandleEquals(input, result);
+    }
+
+    private void assertPhoneAccountEquals(PhoneAccount a, PhoneAccount b) {
+        assertPhoneAccountHandleEquals(a.getAccountHandle(), b.getAccountHandle());
+        assertEquals(a.getHandle(), b.getHandle());
+        assertEquals(a.getSubscriptionNumber(), b.getSubscriptionNumber());
+        assertEquals(a.getCapabilities(), b.getCapabilities());
+        assertEquals(a.getIconResId(), b.getIconResId());
+        assertEquals(a.getLabel(), b.getLabel());
+        assertEquals(a.getShortDescription(), b.getShortDescription());
+    }
+
+    public void testPhoneAccount() throws Exception {
+        PhoneAccount input = makeQuickAccount("pkg0", "cls0", "id0", 0);
+        PhoneAccount result = roundTrip(this, input, PhoneAccountRegistrar.sPhoneAccountXml);
+        assertPhoneAccountEquals(input, result);
+    }
+
+    private void assertStateEquals(PhoneAccountRegistrar.State a, PhoneAccountRegistrar.State b) {
+        assertPhoneAccountHandleEquals(a.defaultOutgoing, b.defaultOutgoing);
+        assertPhoneAccountHandleEquals(a.simCallManager, b.simCallManager);
+        assertEquals(a.accounts.size(), b.accounts.size());
+        for (int i = 0; i < a.accounts.size(); i++) {
+            assertPhoneAccountEquals(a.accounts.get(i), b.accounts.get(i));
+        }
+    }
+
+    public void testState() throws Exception {
+        PhoneAccountRegistrar.State input = makeQuickState();
+        PhoneAccountRegistrar.State result = roundTrip(this, input, PhoneAccountRegistrar.sStateXml);
+        assertStateEquals(input, result);
+    }
+
+    public void testAccounts() throws Exception {
+        assertEquals(4, mRegistrar.getAllPhoneAccountHandles().size());
+        assertEquals(3, mRegistrar.getEnabledPhoneAccounts().size());
+        assertEquals(null, mRegistrar.getSimCallManager());
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+    }
+
+    public void testSimCallManager() throws Exception {
+        // Establish initial conditions
+        assertEquals(null, mRegistrar.getSimCallManager());
+        PhoneAccountHandle h = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
+        mRegistrar.setSimCallManager(h);
+        assertPhoneAccountHandleEquals(h, mRegistrar.getSimCallManager());
+        mRegistrar.unregisterPhoneAccount(h);
+        // If account is un-registered, querying returns null
+        assertEquals(null, mRegistrar.getSimCallManager());
+        // But if account is re-registered, setting comes back
+        mRegistrar.registerPhoneAccount(makeQuickAccount("pkg0", "cls0", "id0", 99));
+        assertPhoneAccountHandleEquals(h, mRegistrar.getSimCallManager());
+        // De-register by setting to null
+        mRegistrar.setSimCallManager(null);
+        assertEquals(null, mRegistrar.getSimCallManager());
+        // If argument not have SIM_CALL_MANAGER capability, this is a no-op
+        mRegistrar.setSimCallManager(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1"));
+        assertEquals(null, mRegistrar.getSimCallManager());
+    }
+
+    public void testDefaultOutgoing() {
+        // Establish initial conditions
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+        PhoneAccountHandle h = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1");
+        mRegistrar.setDefaultOutgoingPhoneAccount(h);
+        assertPhoneAccountHandleEquals(h, mRegistrar.getDefaultOutgoingPhoneAccount());
+        // If account is un-registered, querying returns null
+        mRegistrar.unregisterPhoneAccount(h);
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+        // But if account is re-registered, setting comes back
+        mRegistrar.registerPhoneAccount(makeQuickAccount("pkg0", "cls0", "id1", 99));
+        assertPhoneAccountHandleEquals(h, mRegistrar.getDefaultOutgoingPhoneAccount());
+        // De-register by setting to null
+        mRegistrar.setDefaultOutgoingPhoneAccount(null);
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+        // If argument not have CALL_PROVIDER capability, this is a no-op
+        mRegistrar.setDefaultOutgoingPhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0"));
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+        // If only have one account, it is the default
+        mRegistrar.unregisterPhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0"));
+        mRegistrar.unregisterPhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1"));
+        mRegistrar.unregisterPhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "id2"));
+        assertPhoneAccountHandleEquals(
+                new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "id3"),
+                mRegistrar.getDefaultOutgoingPhoneAccount());
+        // If have one account but not suitable, default returns null
+        mRegistrar.unregisterPhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg1", "cls1"), "id3"));
+        mRegistrar.registerPhoneAccount(new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0"),
+                Uri.parse("tel:555-1212"),
+                "555-1212",
+                PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
+                0,
+                "label0",
+                "desc0"));
+        assertEquals(null, mRegistrar.getDefaultOutgoingPhoneAccount());
+    }
+
+    private static PhoneAccount makeQuickAccount(String pkg, String cls, String id, int idx) {
+        return new PhoneAccount(
+                new PhoneAccountHandle(new ComponentName(pkg, cls), id),
+                Uri.parse("http://foo.com/" + idx),
+                "555-000" + idx,
+                idx,
+                idx,
+                "label" + idx,
+                "desc" + idx);
+    }
+
+    private static PhoneAccountRegistrar.State makeQuickState() {
+        PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
+        s.accounts.add(makeQuickAccount("pkg0", "cls0", "id0", 0));
+        s.accounts.add(makeQuickAccount("pkg0", "cls0", "id1", 1));
+        s.accounts.add(makeQuickAccount("pkg1", "cls1", "id2", 2));
+        s.defaultOutgoing = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
+        s.simCallManager = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id1");
+        return s;
+    }
+}
\ No newline at end of file