Improve Wi-Fi wiring

Bug: 16552606
Change-Id: Iddbde3d18e92ad1d40fb539d9177df582f317a7b
diff --git a/src/com/android/telecomm/AccountSelectionPreference.java b/src/com/android/telecomm/AccountSelectionPreference.java
new file mode 100644
index 0000000..e7c2bc0
--- /dev/null
+++ b/src/com/android/telecomm/AccountSelectionPreference.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.telecomm.PhoneAccountHandle;
+import android.util.AttributeSet;
+
+import java.util.List;
+import java.util.Objects;
+
+public class AccountSelectionPreference extends ListPreference implements
+        Preference.OnPreferenceChangeListener {
+
+    public interface AccountSelectionListener {
+        boolean onAccountSelected(AccountSelectionPreference pref, PhoneAccountHandle account);
+    }
+
+    private AccountSelectionListener mListener;
+    private PhoneAccountHandle[] mAccounts;
+    private String[] mEntryValues;
+    private CharSequence[] mEntries;
+
+    public AccountSelectionPreference(Context context) {
+        super(context);
+        setOnPreferenceChangeListener(this);
+    }
+
+    public AccountSelectionPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setOnPreferenceChangeListener(this);
+    }
+
+    public void setListener(AccountSelectionListener listener) {
+        mListener = listener;
+    }
+
+    public void setModel(
+            PhoneAccountRegistrar registrar,
+            List<PhoneAccountHandle> accountsList,
+            PhoneAccountHandle currentSelection,
+            CharSequence nullSelectionString) {
+        mAccounts = accountsList.toArray(new PhoneAccountHandle[accountsList.size()]);
+        mEntryValues = new String[mAccounts.length + 1];
+        mEntries = new CharSequence[mAccounts.length + 1];
+
+        int selectedIndex = mAccounts.length;  // Points to nullSelectionString by default
+        int i = 0;
+        for ( ; i < mAccounts.length; i++) {
+            CharSequence label = registrar.getPhoneAccount(mAccounts[i]).getLabel();
+            mEntries[i] = label == null ? null : label.toString();
+            mEntryValues[i] = Integer.toString(i);
+            if (Objects.equals(currentSelection, mAccounts[i])) {
+                selectedIndex = i;
+            }
+        }
+        mEntryValues[i] = Integer.toString(i);
+        mEntries[i] = nullSelectionString;
+
+        setEntryValues(mEntryValues);
+        setEntries(mEntries);
+        setValueIndex(selectedIndex);
+        setSummary(mEntries[selectedIndex]);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mListener != null) {
+            int index = Integer.parseInt((String) newValue);
+            PhoneAccountHandle account = index < mAccounts.length ? mAccounts[index] : null;
+            if (mListener.onAccountSelected(this, account)) {
+                setSummary(mEntries[index]);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index a0cb688..0f28974 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class creates connections to place new outgoing calls to attached to an existing incoming
@@ -33,10 +34,35 @@
  *   - a connection service cancels the process, in which case the call is aborted
  */
 final class CreateConnectionProcessor {
+
+    // Describes information required to attempt to make a phone call
+    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;
+        // 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 CallAttemptRecord(
+                PhoneAccountHandle targetConnectionServiceAccount,
+                PhoneAccountHandle phoneAccount) {
+            this.targetConnectionServiceAccount = targetConnectionServiceAccount;
+            this.phoneAccount = phoneAccount;
+        }
+
+        @Override
+        public String toString() {
+            return "CallAttemptRecord("
+                    + Objects.toString(targetConnectionServiceAccount) + ","
+                    + Objects.toString(phoneAccount) + ")";
+        }
+    }
+
     private final Call mCall;
     private final ConnectionServiceRepository mRepository;
-    private List<PhoneAccountHandle> mPhoneAccountHandles;
-    private Iterator<PhoneAccountHandle> mPhoneAccountHandleIterator;
+    private List<CallAttemptRecord> mAttemptRecords;
+    private Iterator<CallAttemptRecord> mAttemptRecordIterator;
     private CreateConnectionResponse mResponse;
     private int mLastErrorCode = DisconnectCause.OUTGOING_FAILURE;
     private String mLastErrorMsg;
@@ -50,12 +76,14 @@
 
     void process() {
         Log.v(this, "process");
-        mPhoneAccountHandles = new ArrayList<>();
+        mAttemptRecords = new ArrayList<>();
         if (mCall.getPhoneAccount() != null) {
-            mPhoneAccountHandles.add(mCall.getPhoneAccount());
+            mAttemptRecords.add(
+                    new CallAttemptRecord(mCall.getPhoneAccount(), mCall.getPhoneAccount()));
         }
-        adjustPhoneAccountsForEmergency();
-        mPhoneAccountHandleIterator = mPhoneAccountHandles.iterator();
+        adjustAttemptsForWifi();
+        adjustAttemptsForEmergency();
+        mAttemptRecordIterator = mAttemptRecords.iterator();
         attemptNextPhoneAccount();
     }
 
@@ -80,16 +108,16 @@
     private void attemptNextPhoneAccount() {
         Log.v(this, "attemptNextPhoneAccount");
 
-        if (mResponse != null && mPhoneAccountHandleIterator.hasNext()) {
-            PhoneAccountHandle accountHandle = mPhoneAccountHandleIterator.next();
-            Log.i(this, "Trying accountHandle %s", accountHandle);
+        if (mResponse != null && mAttemptRecordIterator.hasNext()) {
+            CallAttemptRecord attempt = mAttemptRecordIterator.next();
+            Log.i(this, "Trying attempt %s", attempt);
             ConnectionServiceWrapper service =
-                    mRepository.getService(accountHandle.getComponentName());
+                    mRepository.getService(attempt.targetConnectionServiceAccount.getComponentName());
             if (service == null) {
-                Log.i(this, "Found no connection service for accountHandle %s", accountHandle);
+                Log.i(this, "Found no connection service for attempt %s", attempt);
                 attemptNextPhoneAccount();
             } else {
-                mCall.setPhoneAccount(accountHandle);
+                mCall.setPhoneAccount(attempt.phoneAccount);
                 mCall.setConnectionService(service);
                 Log.i(this, "Attempting to call from %s", service.getComponentName());
                 service.createConnection(mCall, new Response(service));
@@ -104,19 +132,45 @@
         }
     }
 
+    // If there exists a registered Wi-Fi calling service, use it.
+    private void adjustAttemptsForWifi() {
+        switch (mAttemptRecords.size()) {
+            case 0:
+                return;
+            case 1:
+                break;
+            default:
+                Log.d(this, "Unexpectedly have > 1 attempt: %s", mAttemptRecords);
+                return;
+        }
+        PhoneAccountHandle simCallManager =
+                TelecommApp.getInstance().getPhoneAccountRegistrar().getSimCallManager();
+        if (simCallManager != null &&
+                !Objects.equals(simCallManager, mAttemptRecords.get(0).phoneAccount)) {
+            mAttemptRecords.set(
+                    0,
+                    new CallAttemptRecord(
+                            simCallManager,
+                            mAttemptRecords.get(0).phoneAccount));
+        }
+    }
+
     // If we are possibly attempting to call a local emergency number, ensure that the
     // plain PSTN connection services are listed, and nothing else.
-    private void adjustPhoneAccountsForEmergency()  {
+    private void adjustAttemptsForEmergency()  {
         if (TelephonyUtil.shouldProcessAsEmergency(TelecommApp.getInstance(), mCall.getHandle())) {
             Log.i(this, "Emergency number detected");
-            mPhoneAccountHandles.clear();
+            mAttemptRecords.clear();
             List<PhoneAccountHandle> allAccountHandles = TelecommApp.getInstance()
                     .getPhoneAccountRegistrar().getEnabledPhoneAccounts();
             for (int i = 0; i < allAccountHandles.size(); i++) {
                 if (TelephonyUtil.isPstnComponentName(
                         allAccountHandles.get(i).getComponentName())) {
                     Log.i(this, "Will try PSTN account %s for emergency", allAccountHandles.get(i));
-                    mPhoneAccountHandles.add(allAccountHandles.get(i));
+                    mAttemptRecords.add(
+                            new CallAttemptRecord(
+                                    allAccountHandles.get(i),
+                                    allAccountHandles.get(i)));
                 }
             }
         }
diff --git a/src/com/android/telecomm/PhoneAccountPreferencesActivity.java b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
index e4c62e7..2430f26 100644
--- a/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
+++ b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
@@ -18,19 +18,17 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
 import android.preference.PreferenceFragment;
+import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
 
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.Objects;
 
 public class PhoneAccountPreferencesActivity extends Activity {
 
     private static final String KEY_DEFAULT_OUTGOING_ACCOUNT = "default_outgoing_account";
+    private static final String KEY_SIM_CALL_MANAGER_ACCOUNT = "sim_call_manager_account";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -39,54 +37,63 @@
     }
 
     public static class PreferencesFragment extends PreferenceFragment
-            implements ListPreference.OnPreferenceChangeListener {
-        private ListPreference mDefaultOutgoingAccount;
-        private PhoneAccountRegistrar mRegistrar;
-        private Map<String, PhoneAccountHandle> mAccountByValue = new HashMap<>();
+            implements AccountSelectionPreference.AccountSelectionListener {
+        private AccountSelectionPreference mDefaultOutgoingAccount;
+        private AccountSelectionPreference mSimCallManagerAccount;
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
 
             addPreferencesFromResource(R.xml.phone_account_preferences);
-            mDefaultOutgoingAccount = (ListPreference) findPreference(KEY_DEFAULT_OUTGOING_ACCOUNT);
 
-            mRegistrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
-            List<PhoneAccountHandle> accountHandles = mRegistrar.getEnabledPhoneAccounts();
-            PhoneAccountHandle currentDefault = mRegistrar.getDefaultOutgoingPhoneAccount();
+            mDefaultOutgoingAccount = (AccountSelectionPreference)
+                    findPreference(KEY_DEFAULT_OUTGOING_ACCOUNT);
+            mSimCallManagerAccount = (AccountSelectionPreference)
+                    findPreference(KEY_SIM_CALL_MANAGER_ACCOUNT);
 
-            String[] entryValues = new String[accountHandles.size() + 1];
-            String[] entries = new String[accountHandles.size() + 1];
+            PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
 
-            int selectedIndex = accountHandles.size();  // Points to "ask every time" by default
-            int i = 0;
-            for ( ; i < accountHandles.size(); i++) {
-                CharSequence label = mRegistrar.getPhoneAccount(accountHandles.get(i))
-                        .getLabel();
-                entries[i] = label == null ? null : label.toString();
-                entryValues[i] = Integer.toString(i);
-                if (Objects.equals(currentDefault, accountHandles.get(i))) {
-                    selectedIndex = i;
-                }
-                mAccountByValue.put(entryValues[i], accountHandles.get(i));
-            }
-            entryValues[i] = Integer.toString(i);
-            entries[i] = getString(R.string.account_ask_every_time);
-            mAccountByValue.put(entryValues[i], null);
+            mDefaultOutgoingAccount.setModel(
+                    registrar,
+                    registrar.getEnabledPhoneAccounts(),
+                    registrar.getDefaultOutgoingPhoneAccount(),
+                    getString(R.string.account_ask_every_time));
 
-            mDefaultOutgoingAccount.setEntryValues(entryValues);
-            mDefaultOutgoingAccount.setEntries(entries);
-            mDefaultOutgoingAccount.setValueIndex(selectedIndex);
-            mDefaultOutgoingAccount.setOnPreferenceChangeListener(this);
+            mSimCallManagerAccount.setModel(
+                    registrar,
+                    getSimCallManagers(registrar),
+                    registrar.getSimCallManager(),
+                    getString(R.string.do_not_use_sim_call_manager));
+
+            mDefaultOutgoingAccount.setListener(this);
+            mSimCallManagerAccount.setListener(this);
         }
 
         @Override
-        public boolean onPreferenceChange(Preference p, Object o) {
+        public boolean onAccountSelected(
+                AccountSelectionPreference p, PhoneAccountHandle account) {
+            PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
             if (p == mDefaultOutgoingAccount) {
-                mRegistrar.setDefaultOutgoingPhoneAccount(mAccountByValue.get(o));
+                registrar.setDefaultOutgoingPhoneAccount(account);
+                return true;
+            } else if (p == mSimCallManagerAccount) {
+                registrar.setSimCallManager(account);
                 return true;
             }
             return false;
         }
+
+        private List<PhoneAccountHandle> getSimCallManagers(PhoneAccountRegistrar registrar) {
+            List<PhoneAccountHandle> simCallManagers = new ArrayList<>();
+            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) {
+                    simCallManagers.add(allAccounts.get(i));
+                }
+            }
+            return simCallManagers;
+        }
     }
 }
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index 4c781de..f236f58 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -49,50 +49,47 @@
     private static final String PREFERENCE_PHONE_ACCOUNTS = "phone_accounts";
 
     private final Context mContext;
+    private final State mState;
 
     PhoneAccountRegistrar(Context context) {
         mContext = context;
+        mState = readState();
     }
 
     public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
-        State s = read();
-
-        if (s.defaultOutgoingHandle != null) {
+        if (mState.defaultOutgoing != null) {
             // Return the registered outgoing default iff it still exists (we keep a sticky
             // default to survive account deletion and re-addition)
-            for (int i = 0; i < s.accounts.size(); i++) {
-                if (s.accounts.get(i).getAccountHandle().equals(s.defaultOutgoingHandle)) {
-                    return s.defaultOutgoingHandle;
+            for (int i = 0; i < mState.accounts.size(); i++) {
+                if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
+                    return mState.defaultOutgoing;
                 }
             }
-            // At this point, there was a registered default but it has been deleted; remember
-            // it for the future, but return null from this method
-            return null;
-        } else {
-            List<PhoneAccountHandle> enabled = getEnabledPhoneAccounts();
-            switch (enabled.size()) {
-                case 0:
-                    // There are no accounts, so there can be no default
-                    return null;
-                case 1:
-                    // There is only one account, which is by definition the default
-                    return enabled.get(0);
-                default:
-                    // There are multiple accounts with no selected default
-                    return null;
-            }
+            // At this point, there was a registered default but it has been deleted; proceed
+            // as though there were no default
+        }
+
+        List<PhoneAccountHandle> enabled = getEnabledPhoneAccounts();
+        switch (enabled.size()) {
+            case 0:
+                // There are no accounts, so there can be no default
+                return null;
+            case 1:
+                // There is only one account, which is by definition the default
+                return enabled.get(0);
+            default:
+                // There are multiple accounts with no selected default
+                return null;
         }
     }
 
     public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
-        State s = read();
-
         if (accountHandle == null) {
             // Asking to clear the default outgoing is a valid request
-            s.defaultOutgoingHandle = null;
+            mState.defaultOutgoing = null;
         } else {
             boolean found = false;
-            for (PhoneAccount m : s.accounts) {
+            for (PhoneAccount m : mState.accounts) {
                 if (Objects.equals(accountHandle, m.getAccountHandle())) {
                     found = true;
                     break;
@@ -105,21 +102,51 @@
                 return;
             }
 
-            s.defaultOutgoingHandle = accountHandle;
+            mState.defaultOutgoing = accountHandle;
         }
 
-        write(s);
+        write();
     }
 
+    public void setSimCallManager(PhoneAccountHandle callManager) {
+        if (callManager != null) {
+            PhoneAccount callManagerAccount = getPhoneAccount(callManager);
+            if (callManagerAccount == null) {
+                Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
+                return;
+            } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_SIM_CALL_MANAGER)) {
+                Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
+                return;
+            }
+        }
+        mState.simCallManager = callManager;
+        write();
+    }
+
+    public PhoneAccountHandle getSimCallManager() {
+        return mState.simCallManager;
+    }
+
+    public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
+        List<PhoneAccountHandle> accountHandles = new ArrayList<>();
+        for (PhoneAccount m : mState.accounts) {
+            accountHandles.add(m.getAccountHandle());
+        }
+        return accountHandles;
+    }
+
+    public List<PhoneAccount> getAllPhoneAccounts() {
+        return new ArrayList<>(mState.accounts);
+    }
+
+    // TODO: Rename systemwide to "getCallProviderPhoneAccounts"?
     public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
-        State s = read();
-        return simSubscriptionAccountHandles(s);
+        return getCallProviderAccountHandles();
     }
 
-    public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
-        State s = read();
-        for (PhoneAccount m : s.accounts) {
-            if (Objects.equals(accountHandle, m.getAccountHandle())) {
+    public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
+        for (PhoneAccount m : mState.accounts) {
+            if (Objects.equals(handle, m.getAccountHandle())) {
                 return m;
             }
         }
@@ -128,60 +155,112 @@
 
     // TODO: Should we implement an artificial limit for # of accounts associated with a single
     // ComponentName?
-    public void registerPhoneAccount(PhoneAccount metadata) {
-        State s = read();
-
-        s.accounts.add(metadata);
+    public void registerPhoneAccount(PhoneAccount account) {
+        account = hackFixBabelAccount(account);
+        mState.accounts.add(account);
         // Search for duplicates and remove any that are found.
-        for (int i = 0; i < s.accounts.size() - 1; i++) {
-            if (Objects.equals(metadata.getAccountHandle(), s.accounts.get(i).getAccountHandle())) {
+        for (int i = 0; i < mState.accounts.size() - 1; i++) {
+            if (Objects.equals(
+                    account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
                 // replace existing entry.
-                s.accounts.remove(i);
+                mState.accounts.remove(i);
                 break;
             }
         }
 
-        write(s);
+        write();
+    }
+
+    // STOPSHIP: Hack to edit the account registered by Babel so it shows up properly
+    private PhoneAccount hackFixBabelAccount(PhoneAccount account) {
+        String pkg = account.getAccountHandle().getComponentName().getPackageName();
+        return "com.google.android.talk".equals(pkg)
+                ? new PhoneAccount(
+                        account.getAccountHandle(),
+                        account.getHandle(),
+                        account.getSubscriptionNumber(),
+                        PhoneAccount.CAPABILITY_SIM_CALL_MANAGER,
+                        account.getIconResId(),
+                        account.getLabel(),
+                        account.getShortDescription(),
+                        account.isVideoCallingSupported())
+                : account;
     }
 
     public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
-        State s = read();
-
-        for (int i = 0; i < s.accounts.size(); i++) {
-            if (Objects.equals(accountHandle, s.accounts.get(i).getAccountHandle())) {
-                s.accounts.remove(i);
+        for (int i = 0; i < mState.accounts.size(); i++) {
+            if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
+                mState.accounts.remove(i);
                 break;
             }
         }
 
-        write(s);
+        write();
     }
 
     public void clearAccounts(String packageName) {
-        State s = read();
-
-        for (int i = 0; i < s.accounts.size(); i++) {
+        for (int i = 0; i < mState.accounts.size(); i++) {
             if (Objects.equals(
                     packageName,
-                    s.accounts.get(i).getAccountHandle().getComponentName().getPackageName())) {
-                s.accounts.remove(i);
+                    mState.accounts.get(i).getAccountHandle()
+                            .getComponentName().getPackageName())) {
+                mState.accounts.remove(i);
             }
         }
 
-        write(s);
+        write();
     }
 
-    private List<PhoneAccountHandle> simSubscriptionAccountHandles(State s) {
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
+    // Return true iff the given account has all the specified capability flags
+    static boolean has(PhoneAccount account, int capability) {
+        return (account.getCapabilities() & capability) == capability;
+    }
+
+    private List<PhoneAccountHandle> getCallProviderAccountHandles() {
         List<PhoneAccountHandle> accountHandles = new ArrayList<>();
-        for (PhoneAccount m : s.accounts) {
-            if ((m.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
+        for (PhoneAccount m : mState.accounts) {
+            if (has(m, PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
                 accountHandles.add(m.getAccountHandle());
             }
         }
         return accountHandles;
     }
 
-    private State read() {
+    /**
+     * The state of this {@code PhoneAccountRegistrar}.
+     */
+    private 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.
+         */
+        public PhoneAccountHandle defaultOutgoing = null;
+
+        /**
+         * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_SIM_CALL_MANAGER} which
+         * manages and optimizes a user's PSTN SIM connections.
+         */
+        public PhoneAccountHandle simCallManager;
+
+        /**
+         * The complete list of {@code PhoneAccount}s known to the Telecomm subsystem.
+         */
+        public final List<PhoneAccount> accounts = new ArrayList<>();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //
+    // State management
+    //
+
+    private void write() {
+        writeState(mState);
+    }
+
+    private State readState() {
         try {
             String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
             Log.v(this, "read() obtained serialized state: %s", serialized);
@@ -196,7 +275,7 @@
         }
     }
 
-    private boolean write(State state) {
+    private boolean writeState(State state) {
         try {
             Log.v(this, "write() writing state: %s", state);
             String serialized = serializeState(state);
@@ -226,11 +305,7 @@
         return sStateJson.fromJson(new JSONObject(new JSONTokener(s)));
     }
 
-    private static class State {
-        public PhoneAccountHandle defaultOutgoingHandle = null;
-        public final List<PhoneAccount> accounts = new ArrayList<>();
-    }
-
+    ////////////////////////////////////////////////////////////////////////////////////////////////
     //
     // JSON serialization
     //
@@ -243,17 +318,21 @@
     private static final Json<State> sStateJson =
             new Json<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();
-            if (o.defaultOutgoingHandle != null) {
-                json.put(DEFAULT_OUTGOING, sPhoneAccountJson.toJson(o.defaultOutgoingHandle));
+            if (o.defaultOutgoing != null) {
+                json.put(DEFAULT_OUTGOING, sPhoneAccountHandleJson.toJson(o.defaultOutgoing));
+            }
+            if (o.simCallManager != null) {
+                json.put(SIM_CALL_MANAGER, sPhoneAccountHandleJson.toJson(o.simCallManager));
             }
             JSONArray accounts = new JSONArray();
             for (PhoneAccount m : o.accounts) {
-                accounts.put(sPhoneAccountMetadataJson.toJson(m));
+                accounts.put(sPhoneAccountJson.toJson(m));
             }
             json.put(ACCOUNTS, accounts);
             return json;
@@ -263,14 +342,26 @@
         public State fromJson(JSONObject json) throws JSONException {
             State s = new State();
             if (json.has(DEFAULT_OUTGOING)) {
-                s.defaultOutgoingHandle = sPhoneAccountJson.fromJson(
-                        (JSONObject) json.get(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(sPhoneAccountMetadataJson.fromJson(
+                        s.accounts.add(sPhoneAccountJson.fromJson(
                                 (JSONObject) accounts.get(i)));
                     } catch (Exception e) {
                         Log.e(this, e, "Extracting phone account");
@@ -281,7 +372,7 @@
         }
     };
 
-    private static final Json<PhoneAccount> sPhoneAccountMetadataJson =
+    private static final Json<PhoneAccount> sPhoneAccountJson =
             new Json<PhoneAccount>() {
         private static final String ACCOUNT = "account";
         private static final String HANDLE = "handle";
@@ -295,7 +386,7 @@
         @Override
         public JSONObject toJson(PhoneAccount o) throws JSONException {
             return new JSONObject()
-                    .put(ACCOUNT, sPhoneAccountJson.toJson(o.getAccountHandle()))
+                    .put(ACCOUNT, sPhoneAccountHandleJson.toJson(o.getAccountHandle()))
                     .put(HANDLE, o.getHandle().toString())
                     .put(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber())
                     .put(CAPABILITIES, o.getCapabilities())
@@ -308,7 +399,7 @@
         @Override
         public PhoneAccount fromJson(JSONObject json) throws JSONException {
             return new PhoneAccount(
-                    sPhoneAccountJson.fromJson((JSONObject) json.get(ACCOUNT)),
+                    sPhoneAccountHandleJson.fromJson((JSONObject) json.get(ACCOUNT)),
                     Uri.parse((String) json.get(HANDLE)),
                     (String) json.get(SUBSCRIPTION_NUMBER),
                     (int) json.get(CAPABILITIES),
@@ -319,7 +410,7 @@
         }
     };
 
-    private static final Json<PhoneAccountHandle> sPhoneAccountJson =
+    private static final Json<PhoneAccountHandle> sPhoneAccountHandleJson =
             new Json<PhoneAccountHandle>() {
         private static final String COMPONENT_NAME = "component_name";
         private static final String ID = "id";
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index b0fc709..3598422 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -193,7 +193,8 @@
         try {
             enforceModifyPermissionOrCallingPackage(
                     account.getAccountHandle().getComponentName().getPackageName());
-            if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
+            if (PhoneAccountRegistrar.has(account, PhoneAccount.CAPABILITY_CALL_PROVIDER) ||
+                PhoneAccountRegistrar.has(account, PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                 enforceModifyPermissionOrCallingPackage(TELEPHONY_PACKAGE_NAME);
             }
             mPhoneAccountRegistrar.registerPhoneAccount(account);