Merge "Fixing ADD_CALL (2/3)" into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2f8fc6f..eacf5cf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -181,5 +181,15 @@
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".PhoneAccountPreferencesActivity"
+                  android:label="@string/phone_account_preferences_title"
+                  android:configChanges="orientation|screenSize|keyboardHidden">
+                  android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/res/layout/phone_account_preferences.xml b/res/layout/phone_account_preferences.xml
new file mode 100644
index 0000000..caf2a47
--- /dev/null
+++ b/res/layout/phone_account_preferences.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <fragment
+            android:id="@+id/preferences_fragment"
+            class="com.android.telecomm.PhoneAccountPreferencesActivity$PreferencesFragment"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eddeec5..4d19197 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -76,6 +76,17 @@
         a text response. [CHAR LIMIT=40] -->
     <string name="respond_via_sms_confirmation_format">Message sent to <xliff:g id="phone_number">%s</xliff:g>.</string>
 
+    <!-- Title for phone accounts settings screen -->
+    <string name="phone_account_preferences_title">Phone account preferences</string>
+
+    <!-- Title for setting to select default outgoing phone account -->
+    <string name="default_outgoing_account_title">Default outgoing account</string>
+
+    <!-- Summary for setting to select default outgoing phone account -->
+    <string name="default_outgoing_account_summary">Select a phone account to use by default for making outgoing calls</string>
+
+    <!-- Indication to "ask every time" for accounts when making a call -->
+    <string name="account_ask_every_time">Ask every time</string>
 
     <!-- DO NOT TRANSLATE. Label for test Subscription 0. -->
     <string name="test_account_0_label">Q Mobile</string>
diff --git a/res/xml/phone_account_preferences.xml b/res/xml/phone_account_preferences.xml
new file mode 100644
index 0000000..7f12306
--- /dev/null
+++ b/res/xml/phone_account_preferences.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Settings screen for selecting which PhoneAccount to use by default
+     for making outgoing phone calls -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+     android:title="@string/phone_account_preferences_title">
+    <ListPreference
+        android:key="default_outgoing_account"
+        android:title="@string/default_outgoing_account_title"
+        android:summary="@string/default_outgoing_account_summary"
+        android:defaultValue=""
+        android:persistent="false" />
+</PreferenceScreen>
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 27a995a..0eeb21e 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -16,6 +16,7 @@
 
 package com.android.telecomm;
 
+import android.app.PendingIntent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -78,6 +79,8 @@
         void onHandleChanged(Call call);
         void onCallerDisplayNameChanged(Call call);
         void onVideoStateChanged(Call call);
+        void onStartActivityFromInCall(Call call, PendingIntent intent);
+        void onPhoneAccountChanged(Call call);
     }
 
     abstract static class ListenerBase implements Listener {
@@ -121,6 +124,10 @@
         public void onCallerDisplayNameChanged(Call call) {}
         @Override
         public void onVideoStateChanged(Call call) {}
+        @Override
+        public void onStartActivityFromInCall(Call call, PendingIntent intent) {}
+        @Override
+        public void onPhoneAccountChanged(Call call) {}
     }
 
     private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -436,7 +443,12 @@
     }
 
     void setPhoneAccount(PhoneAccount account) {
-        mPhoneAccount = account;
+        if (!Objects.equals(mPhoneAccount, account)) {
+            mPhoneAccount = account;
+            for (Listener l : mListeners) {
+                l.onPhoneAccountChanged(this);
+            }
+        }
     }
 
     boolean isIncoming() {
@@ -1122,4 +1134,14 @@
             l.onStatusHintsChanged(this);
         }
     }
+
+    public void startActivityFromInCall(PendingIntent intent) {
+        if (intent.isActivity()) {
+            for (Listener l : mListeners) {
+                l.onStartActivityFromInCall(this, intent);
+            }
+        } else {
+            Log.w(this, "startActivityFromInCall, activity intent required");
+        }
+    }
 }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 9dbcf18..5a53525 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -62,9 +62,6 @@
 
     private static final CallsManager INSTANCE = new CallsManager();
 
-    /** Temporary flag for disabling account selection menu */
-    public static final boolean ENABLE_ACCOUNT_SELECT = false;
-
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
      * calls are added to the map and removed when the calls move to the disconnected state.
@@ -314,13 +311,20 @@
         call.addListener(this);
         addCall(call);
 
-        // TODO: check for default account
-        if (account == null && ENABLE_ACCOUNT_SELECT) {
-            call.setState(CallState.PRE_DIAL_WAIT);
-            return;
+        if (TelephonyUtil.shouldProcessAsEmergency(TelecommApp.getInstance(), call.getHandle())) {
+            // Emergency -- CreateConnectionProcessor will choose accounts automatically
+            call.setPhoneAccount(null);
+            call.startCreateConnection();
+        } else if (account == null) {
+            PhoneAccount defaultAccount = TelecommApp.getInstance().getPhoneAccountRegistrar()
+                    .getDefaultOutgoingPhoneAccount();
+            if (defaultAccount != null) {
+                call.setPhoneAccount(defaultAccount);
+                call.startCreateConnection();
+            } else {
+                call.setState(CallState.PRE_DIAL_WAIT);
+            }
         }
-
-        call.startCreateConnection();
     }
 
     /**
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index fb9d34c..eb2e0ac 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.telecomm;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Bundle;
@@ -77,6 +78,7 @@
     private static final int MSG_SET_HANDLE = 19;
     private static final int MSG_SET_CALLER_DISPLAY_NAME = 20;
     private static final int MSG_SET_VIDEO_STATE = 21;
+    private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 22;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -171,17 +173,11 @@
                     }
                     break;
                 case MSG_SET_REQUESTING_RINGBACK: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        boolean ringback = (boolean) args.arg2;
-                        if (call != null) {
-                            call.setRequestingRingback(ringback);
-                        } else {
-                            //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        call.setRequestingRingback(msg.arg1 == 1);
+                    } else {
+                        //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
                     }
                     break;
                 }
@@ -315,6 +311,18 @@
                         call.setVideoState(msg.arg1);
                     }
                 }
+                case MSG_START_ACTIVITY_FROM_IN_CALL: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        call = mCallIdMapper.getCall(args.arg1);
+                        if (call != null) {
+                            call.startActivityFromInCall((PendingIntent) args.arg2);
+                        }
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
             }
         }
     };
@@ -400,10 +408,8 @@
         public void setRequestingRingback(String callId, boolean ringback) {
             logIncoming("setRequestingRingback %s %b", callId, ringback);
             mCallIdMapper.checkValidCallId(callId);
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = ringback;
-            mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, args).sendToTarget();
+            mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, ringback ? 1 : 0, 0, callId)
+                    .sendToTarget();
         }
 
         @Override
@@ -497,6 +503,16 @@
             args.argi1 = presentation;
             mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
         }
+
+        @Override
+        public void startActivityFromInCall(String callId, PendingIntent intent) {
+            logIncoming("startActivityFromInCall %s %s", callId, intent);
+            mCallIdMapper.checkValidCallId(callId);
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = intent;
+            mHandler.obtainMessage(MSG_START_ACTIVITY_FROM_IN_CALL, args).sendToTarget();
+        }
     }
 
     private final Adapter mAdapter = new Adapter();
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index 631d6fe..45008c0 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -16,16 +16,13 @@
 
 package com.android.telecomm;
 
-import android.content.ComponentName;
-import android.net.Uri;
+import android.telecomm.PhoneAccount;
 import android.telephony.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
 import android.telecomm.ConnectionRequest;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * This class creates connections to place new outgoing calls to attached to an existing incoming
@@ -37,8 +34,8 @@
 final class CreateConnectionProcessor {
     private final Call mCall;
     private final ConnectionServiceRepository mRepository;
-    private List<ComponentName> mServiceComponentNames;
-    private Iterator<ComponentName> mServiceComponentNameIterator;
+    private List<PhoneAccount> mPhoneAccounts;
+    private Iterator<PhoneAccount> mPhoneAccountIterator;
     private CreateConnectionResponse mResponse;
     private int mLastErrorCode = DisconnectCause.ERROR_UNSPECIFIED;
     private String mLastErrorMsg;
@@ -52,31 +49,13 @@
 
     void process() {
         Log.v(this, "process");
-
-        mServiceComponentNames = new ArrayList<>();
-
-        // TODO(sail): Remove once there's a way to pick the service.
-        ArrayList<ComponentName> priorityComponents = new ArrayList<>();
-        priorityComponents.add(new ComponentName("com.android.phone",
-                "com.android.services.telephony.sip.SipConnectionService"));
-        priorityComponents.add(new ComponentName("com.google.android.talk",
-                "com.google.android.apps.babel.telephony.TeleConnectionService"));
-        priorityComponents.add(new ComponentName("com.android.telecomm.tests",
-                "com.android.telecomm.testapps.TestConnectionService"));
-
-        for (ConnectionServiceWrapper service : mRepository.lookupServices()) {
-            ComponentName serviceName = service.getComponentName();
-            if (priorityComponents.contains(serviceName)) {
-                Log.i(this, "Moving connection service %s to top of list", serviceName);
-                mServiceComponentNames .add(0, serviceName);
-            } else {
-                mServiceComponentNames.add(serviceName);
-            }
+        mPhoneAccounts = new ArrayList<>();
+        if (mCall.getPhoneAccount() != null) {
+            mPhoneAccounts.add(mCall.getPhoneAccount());
         }
-
-        adjustComponentNamesForEmergency();
-        mServiceComponentNameIterator = mServiceComponentNames.iterator();
-        attemptNextConnectionService();
+        adjustPhoneAccountsForEmergency();
+        mPhoneAccountIterator = mPhoneAccounts.iterator();
+        attemptNextPhoneAccount();
     }
 
     void abort() {
@@ -97,21 +76,24 @@
         }
     }
 
-    private void attemptNextConnectionService() {
-        Log.v(this, "attemptNextConnectionService");
+    private void attemptNextPhoneAccount() {
+        Log.v(this, "attemptNextPhoneAccount");
 
-        if (mResponse != null && mServiceComponentNameIterator.hasNext()) {
-            ComponentName component = mServiceComponentNameIterator.next();
-            ConnectionServiceWrapper service = mRepository.getService(component);
+        if (mResponse != null && mPhoneAccountIterator.hasNext()) {
+            PhoneAccount account = mPhoneAccountIterator.next();
+            Log.i(this, "Trying account %s", account);
+            ConnectionServiceWrapper service = mRepository.getService(account.getComponentName());
             if (service == null) {
-                attemptNextConnectionService();
+                Log.i(this, "Found no connection service for account %s", account);
+                attemptNextPhoneAccount();
             } else {
+                mCall.setPhoneAccount(account);
                 mCall.setConnectionService(service);
                 Log.i(this, "Attempting to call from %s", service.getComponentName());
                 service.createConnection(mCall, new Response(service));
             }
         } else {
-            Log.v(this, "attemptNextConnectionService, no more services, failing");
+            Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
             if (mResponse != null) {
                 mResponse.handleCreateConnectionFailed(mLastErrorCode, mLastErrorMsg);
                 mResponse = null;
@@ -121,23 +103,22 @@
     }
 
     // If we are possibly attempting to call a local emergency number, ensure that the
-    // plain PSTN connection service, if it exists, is attempted first.
-    private void adjustComponentNamesForEmergency()  {
-        if (shouldProcessAsEmergency(mCall.getHandle())) {
-            for (int i = 0; i < mServiceComponentNames.size(); i++) {
-                if (TelephonyUtil.isPstnComponentName(mServiceComponentNames.get(i))) {
-                    mServiceComponentNames.add(0, mServiceComponentNames.remove(i));
-                    return;
+    // plain PSTN connection services are listed, and nothing else.
+    private void adjustPhoneAccountsForEmergency()  {
+        if (TelephonyUtil.shouldProcessAsEmergency(TelecommApp.getInstance(), mCall.getHandle())) {
+            Log.i(this, "Emergency number detected");
+            mPhoneAccounts.clear();
+            List<PhoneAccount> allAccounts = TelecommApp.getInstance().getPhoneAccountRegistrar()
+                    .getEnabledPhoneAccounts();
+            for (int i = 0; i < allAccounts.size(); i++) {
+                if (TelephonyUtil.isPstnComponentName(allAccounts.get(i).getComponentName())) {
+                    Log.i(this, "Will try PSTN account %s for emergency", allAccounts.get(i));
+                    mPhoneAccounts.add(allAccounts.get(i));
                 }
             }
         }
     }
 
-    private boolean shouldProcessAsEmergency(Uri handle) {
-        return handle != null && PhoneNumberUtils.isPotentialLocalEmergencyNumber(
-                TelecommApp.getInstance(), handle.getSchemeSpecificPart());
-    }
-
     private class Response implements CreateConnectionResponse {
         private final ConnectionServiceWrapper mService;
 
@@ -159,7 +140,8 @@
         public void handleCreateConnectionFailed(int code, String msg) {
             mLastErrorCode = code;
             mLastErrorMsg = msg;
-            attemptNextConnectionService();
+            Log.d(CreateConnectionProcessor.this, "Connection failed: %d (%s)", code, msg);
+            attemptNextPhoneAccount();
         }
 
         @Override
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 8e973f6..e11d60e 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -16,6 +16,7 @@
 
 package com.android.telecomm;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -95,6 +96,22 @@
         public void onVideoStateChanged(Call call) {
             updateCall(call);
         }
+
+        @Override
+        public void onStartActivityFromInCall(Call call, PendingIntent intent) {
+            if (mInCallService != null) {
+                Log.i(this, "Calling startActivity, intent: %s", intent);
+                try {
+                    mInCallService.startActivity(mCallIdMapper.getCallId(call), intent);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        @Override
+        public void onPhoneAccountChanged(Call call) {
+            updateCall(call);
+        }
     };
 
     /** Maintains a binding connection to the in-call app. */
diff --git a/src/com/android/telecomm/PhoneAccountPreferencesActivity.java b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
new file mode 100644
index 0000000..2988a9c
--- /dev/null
+++ b/src/com/android/telecomm/PhoneAccountPreferencesActivity.java
@@ -0,0 +1,92 @@
+/*
+ * 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.app.Activity;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.telecomm.PhoneAccount;
+
+import java.util.HashMap;
+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";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.phone_account_preferences);
+    }
+
+    public static class PreferencesFragment extends PreferenceFragment
+            implements ListPreference.OnPreferenceChangeListener {
+        private ListPreference mDefaultOutgoingAccount;
+        private PhoneAccountRegistrar mRegistrar;
+        private Map<String, PhoneAccount> mAccountByValue = new HashMap<>();
+
+        @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<PhoneAccount> accounts = mRegistrar.getEnabledPhoneAccounts();
+            PhoneAccount currentDefault = mRegistrar.getDefaultOutgoingPhoneAccount();
+
+            String[] entryValues = new String[accounts.size() + 1];
+            String[] entries = new String[accounts.size() + 1];
+
+            int selectedIndex = accounts.size();  // Points to "ask every time" by default
+            int i = 0;
+            for ( ; i < accounts.size(); i++) {
+                entryValues[i] = Integer.toString(i);
+                entries[i] = mRegistrar
+                        .getPhoneAccountMetadata(accounts.get(i))
+                        .getLabel();
+                if (Objects.equals(currentDefault, accounts.get(i))) {
+                    selectedIndex = i;
+                }
+                mAccountByValue.put(entryValues[i], accounts.get(i));
+            }
+            entryValues[i] = Integer.toString(i);
+            entries[i] = getString(R.string.account_ask_every_time);
+            mAccountByValue.put(entryValues[i], null);
+
+            mDefaultOutgoingAccount.setEntryValues(entryValues);
+            mDefaultOutgoingAccount.setEntries(entries);
+            mDefaultOutgoingAccount.setValueIndex(selectedIndex);
+            mDefaultOutgoingAccount.setOnPreferenceChangeListener(this);
+        }
+
+        @Override
+        public boolean onPreferenceChange(Preference p, Object o) {
+            if (p == mDefaultOutgoingAccount) {
+                mRegistrar.setDefaultOutgoingPhoneAccount(mAccountByValue.get(o));
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index e8abb8a..d089e17 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -16,209 +16,310 @@
 
 package com.android.telecomm;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
 import android.content.ComponentName;
 import android.content.Context;
 
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
 import android.net.Uri;
-import android.provider.Settings;
 import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountMetadata;
+import android.telecomm.TelecommManager;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
 /**
- * Handles writing and reading PhoneAccount registration entries.
+ * Handles writing and reading PhoneAccount registration entries. This is a simple verbatim
+ * delegate for all the account handling methods on {@link TelecommManager} as implemented in
+ * {@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 PhoneAccount}s.
+ *
  * TODO(santoscordon): Replace this implementation with a proper database stored in a Telecomm
  * provider.
  */
 final class PhoneAccountRegistrar {
-    private static final int VERSION = 1;
     private static final String TELECOMM_PREFERENCES = "telecomm_prefs";
     private static final String PREFERENCE_PHONE_ACCOUNTS = "phone_accounts";
 
     private final Context mContext;
 
-    private final class DeserializationToken {
-        int currentIndex = 0;
-        final String source;
-
-        DeserializationToken(String source) {
-            this.source = source;
-        }
-    }
-
     PhoneAccountRegistrar(Context context) {
         mContext = context;
     }
 
-    /**
-     * Adds a new phone account entry or updates an existing one.
-     */
-    boolean addAccount(PhoneAccount account) {
-        List<PhoneAccount> allAccounts = getAllAccounts();
-        // Should we implement an artificial limit for # of accounts associated with a single
-        // ComponentName?
-        allAccounts.add(account);
+    public PhoneAccount getDefaultOutgoingPhoneAccount() {
+        State s = read();
+        return s.defaultOutgoing;
+    }
 
-        // Search for duplicates and remove any that are found.
-        for (int i = 0; i < allAccounts.size() - 1; i++) {
-            if (account.equalsComponentAndId(allAccounts.get(i))) {
-                // replace existing entry.
-                allAccounts.remove(i);
-                break;
+    public void setDefaultOutgoingPhoneAccount(PhoneAccount account) {
+        State s = read();
+
+        if (account == null) {
+            // Asking to clear the default outgoing is a valid request
+            s.defaultOutgoing = null;
+        } else {
+            boolean found = false;
+            for (PhoneAccountMetadata m : s.accounts) {
+                if (Objects.equals(account, m.getAccount())) {
+                    found = true;
+                    break;
+                }
             }
+
+            if (!found) {
+                Log.d(this, "Trying to set nonexistent default outgoing phone account %s", account);
+                return;
+            }
+
+            s.defaultOutgoing = account;
         }
 
-        return writeAllAccounts(allAccounts);
+        write(s);
     }
 
-    /**
-     * Removes an existing phone account entry.
-     */
-    boolean removeAccount(PhoneAccount account) {
-        List<PhoneAccount> allAccounts = getAllAccounts();
-
-        for (int i = 0; i < allAccounts.size(); i++) {
-            if (account.equalsComponentAndId(allAccounts.get(i))) {
-                allAccounts.remove(i);
-                return writeAllAccounts(allAccounts);
-            }
-        }
-
-        return false;
+    public List<PhoneAccount> getEnabledPhoneAccounts() {
+        State s = read();
+        return accountsOnly(s);
     }
 
-    /**
-     * Returns a list of all accounts which the user has enabled.
-     */
-    List<PhoneAccount> getEnabledAccounts() {
-        List<PhoneAccount> allAccounts = getAllAccounts();
-        // TODO: filter list
-        return allAccounts;
-    }
-
-    /**
-     * Returns the list of all accounts registered with the system, whether or not the user
-     * has explicitly enabled them.
-     */
-    List<PhoneAccount> getAllAccounts() {
-        String value = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
-        return deserializeAllAccounts(value);
-    }
-
-    /**
-     * Returns the registered version of the account matching the component name and ID of the
-     * specified account.
-     */
-    PhoneAccount getRegisteredAccount(PhoneAccount account) {
-        for (PhoneAccount registeredAccount : getAllAccounts()) {
-            if (registeredAccount.equalsComponentAndId(account)) {
-                return registeredAccount;
+    public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
+        State s = read();
+        for (PhoneAccountMetadata m : s.accounts) {
+            if (Objects.equals(account, m.getAccount())) {
+                return m;
             }
         }
         return null;
     }
 
-    /**
-     * Replaces the contents of our list of accounts with this new list.
-     */
-    private boolean writeAllAccounts(List<PhoneAccount> allAccounts) {
-        Editor editor = getPreferences().edit();
-        editor.putString(PREFERENCE_PHONE_ACCOUNTS, serializeAllAccounts(allAccounts));
-        return editor.commit();
-    }
+    // TODO: Should we implement an artificial limit for # of accounts associated with a single
+    // ComponentName?
+    public void registerPhoneAccount(PhoneAccountMetadata metadata) {
+        State s = read();
 
-    // Serialization implementation
-    // Serializes all strings into the format "len:string-value"
-    // Example, we will serialize the following PhoneAccount.
-    //   PhoneAccount
-    //     ComponentName: "abc"
-    //     Id:            "def"
-    //     Handle:        "555"
-    //     Capabilities:  1
-    //
-    //  Each value serializes into (spaces added for readability)
-    //    3:abc 3:def 3:555 1:1
-    //
-    //  Two identical accounts would likewise be serialized as a list of strings with a prepended
-    //  size of 2.
-    //    1:2 3:abc 3:def 3:555 1:1 3:abc 3:def 3:555 1:1
-    //
-    //  The final result with a prepended version ("1:1") would be:
-    //    "1:11:23:abc3:def3:5551:13:abc3:def3:5551:1"
-
-    private String serializeAllAccounts(List<PhoneAccount> allAccounts) {
-        StringBuilder buffer = new StringBuilder();
-
-        // Version
-        serializeIntValue(VERSION, buffer);
-
-        // Number of accounts
-        serializeIntValue(allAccounts.size(), buffer);
-
-        // The actual accounts
-        for (int i = 0; i < allAccounts.size(); i++) {
-            PhoneAccount account = allAccounts.get(i);
-            serializeStringValue(account.getComponentName().flattenToShortString(), buffer);
-            serializeStringValue(account.getId(), buffer);
-            serializeStringValue(account.getHandle().toString(), buffer);
-            serializeIntValue(account.getCapabilities(), buffer);
-        }
-
-        return buffer.toString();
-    }
-
-    private List<PhoneAccount> deserializeAllAccounts(String source) {
-        List<PhoneAccount> accounts = new ArrayList<PhoneAccount>();
-
-        if (source != null) {
-            DeserializationToken token = new DeserializationToken(source);
-            int version = deserializeIntValue(token);
-            if (version == 1) {
-                int size = deserializeIntValue(token);
-
-                for (int i = 0; i < size; i++) {
-                    String strComponentName = deserializeStringValue(token);
-                    String strId = deserializeStringValue(token);
-                    String strHandle = deserializeStringValue(token);
-                    int capabilities = deserializeIntValue(token);
-
-                    accounts.add(new PhoneAccount(
-                            ComponentName.unflattenFromString(strComponentName),
-                            strId,
-                            Uri.parse(strHandle),
-                            capabilities));
-                }
+        s.accounts.add(metadata);
+        // Search for duplicates and remove any that are found.
+        for (int i = 0; i < s.accounts.size() - 1; i++) {
+            if (Objects.equals(metadata.getAccount(), s.accounts.get(i).getAccount())) {
+                // replace existing entry.
+                s.accounts.remove(i);
+                break;
             }
         }
 
-        return accounts;
+        write(s);
     }
 
-    private void serializeIntValue(int value, StringBuilder buffer) {
-        serializeStringValue(String.valueOf(value), buffer);
+    public void unregisterPhoneAccount(PhoneAccount account) {
+        State s = read();
+
+        for (int i = 0; i < s.accounts.size(); i++) {
+            if (Objects.equals(account, s.accounts.get(i).getAccount())) {
+                s.accounts.remove(i);
+                break;
+            }
+        }
+
+        checkDefaultOutgoing(s);
+
+        write(s);
     }
 
-    private void serializeStringValue(String value, StringBuilder buffer) {
-        buffer.append(value.length()).append(":").append(value);
+    public void clearAccounts(String packageName) {
+        State s = read();
+
+        for (int i = 0; i < s.accounts.size(); i++) {
+            if (Objects.equals(
+                    packageName,
+                    s.accounts.get(i).getAccount().getComponentName().getPackageName())) {
+                s.accounts.remove(i);
+            }
+        }
+
+        checkDefaultOutgoing(s);
+
+        write(s);
     }
 
-    private int deserializeIntValue(DeserializationToken token) {
-        return Integer.parseInt(deserializeStringValue(token));
+    private void checkDefaultOutgoing(State s) {
+        // Check that, after an operation that removes accounts, the account set up as the "default
+        // outgoing" has not been deleted. If it has, then clear out the setting.
+        for (PhoneAccountMetadata m : s.accounts) {
+            if (Objects.equals(s.defaultOutgoing, m.getAccount())) {
+                return;
+            }
+        }
+        s.defaultOutgoing = null;
     }
 
-    private String deserializeStringValue(DeserializationToken token) {
-        int colonIndex = token.source.indexOf(':', token.currentIndex);
-        int valueLength = Integer.parseInt(token.source.substring(token.currentIndex, colonIndex));
-        int endIndex = colonIndex + 1 + valueLength;
-        token.currentIndex = endIndex;
-        return token.source.substring(colonIndex + 1, endIndex);
+    private List<PhoneAccount> accountsOnly(State s) {
+        List<PhoneAccount> result = new ArrayList<>();
+        for (PhoneAccountMetadata m : s.accounts) {
+            result.add(m.getAccount());
+        }
+        return result;
+    }
+
+    private State read() {
+        try {
+            String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
+            Log.d(this, "read() obtained serialized state: %s", serialized);
+            State state = serialized == null
+                    ? new State()
+                    : deserializeState(serialized);
+            Log.d(this, "read() obtained state: %s", state);
+            return state;
+        } catch (JSONException e) {
+            Log.e(this, e, "read");
+            return new State();
+        }
+    }
+
+    private boolean write(State state) {
+        try {
+            Log.d(this, "write() writing state: %s", state);
+            String serialized = serializeState(state);
+            Log.d(this, "write() writing serialized state: %s", serialized);
+            boolean success = getPreferences()
+                    .edit()
+                    .putString(PREFERENCE_PHONE_ACCOUNTS, serialized)
+                    .commit();
+            Log.d(this, "serialized state was written with succcess = %b", success);
+            return success;
+        } catch (JSONException e) {
+            Log.e(this, e, "write");
+            return false;
+        }
     }
 
     private SharedPreferences getPreferences() {
         return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
     }
+
+    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 class State {
+        public PhoneAccount defaultOutgoing = null;
+        public final List<PhoneAccountMetadata> accounts = new ArrayList<>();
+    }
+
+    //
+    // JSON serialization
+    //
+
+    private interface Json<T> {
+        JSONObject toJson(T o) throws JSONException;
+        T fromJson(JSONObject json) throws JSONException;
+    }
+
+    private static final Json<State> sStateJson =
+            new Json<State>() {
+        private static final String DEFAULT_OUTGOING = "default_outgoing";
+        private static final String ACCOUNTS = "accounts";
+
+        @Override
+        public JSONObject toJson(State o) throws JSONException {
+            JSONObject json = new JSONObject();
+            if (o.defaultOutgoing != null) {
+                json.put(DEFAULT_OUTGOING, sPhoneAccountJson.toJson(o.defaultOutgoing));
+            }
+            JSONArray accounts = new JSONArray();
+            for (PhoneAccountMetadata m : o.accounts) {
+                accounts.put(sPhoneAccountMetadataJson.toJson(m));
+            }
+            json.put(ACCOUNTS, accounts);
+            return json;
+        }
+
+        @Override
+        public State fromJson(JSONObject json) throws JSONException {
+            State s = new State();
+            if (json.has(DEFAULT_OUTGOING)) {
+                s.defaultOutgoing = sPhoneAccountJson.fromJson(
+                        (JSONObject) json.get(DEFAULT_OUTGOING));
+            }
+            if (json.has(ACCOUNTS)) {
+                JSONArray accounts = (JSONArray) json.get(ACCOUNTS);
+                for (int i = 0; i < accounts.length(); i++) {
+                    try {
+                        s.accounts.add(sPhoneAccountMetadataJson.fromJson(
+                                (JSONObject) accounts.get(i)));
+                    } catch (Exception e) {
+                        Log.e(this, e, "Extracting phone account");
+                    }
+                }
+            }
+            return s;
+        }
+    };
+
+    private static final Json<PhoneAccountMetadata> sPhoneAccountMetadataJson =
+            new Json<PhoneAccountMetadata>() {
+        private static final String ACCOUNT = "account";
+        private static final String HANDLE = "handle";
+        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(PhoneAccountMetadata o) throws JSONException {
+            return new JSONObject()
+                    .put(ACCOUNT, sPhoneAccountJson.toJson(o.getAccount()))
+                    .put(HANDLE, o.getHandle().toString())
+                    .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());
+        }
+
+        @Override
+        public PhoneAccountMetadata fromJson(JSONObject json) throws JSONException {
+            return new PhoneAccountMetadata(
+                    sPhoneAccountJson.fromJson((JSONObject) json.get(ACCOUNT)),
+                    Uri.parse((String) json.get(HANDLE)),
+                    (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));
+        }
+    };
+
+    private static final Json<PhoneAccount> sPhoneAccountJson =
+            new Json<PhoneAccount>() {
+        private static final String COMPONENT_NAME = "component_name";
+        private static final String ID = "id";
+
+        @Override
+        public JSONObject toJson(PhoneAccount o) throws JSONException {
+            return new JSONObject()
+                    .put(COMPONENT_NAME, o.getComponentName().flattenToString())
+                    .put(ID, o.getId());
+        }
+
+        @Override
+        public PhoneAccount fromJson(JSONObject json) throws JSONException {
+            return new PhoneAccount(
+                    ComponentName.unflattenFromString((String) json.get(COMPONENT_NAME)),
+                    (String) json.get(ID));
+        }
+    };
 }
diff --git a/src/com/android/telecomm/TelecommApp.java b/src/com/android/telecomm/TelecommApp.java
index 2b2f160..fd35708 100644
--- a/src/com/android/telecomm/TelecommApp.java
+++ b/src/com/android/telecomm/TelecommApp.java
@@ -17,7 +17,12 @@
 package com.android.telecomm;
 
 import android.app.Application;
+import android.content.ComponentName;
+import android.net.Uri;
 import android.os.UserHandle;
+import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountMetadata;
+import android.telephony.PhoneNumberUtils;
 
 /**
  * Top-level Application class for Telecomm.
@@ -34,7 +39,7 @@
     private MissedCallNotifier mMissedCallNotifier;
 
     /**
-     * Maintains the list of registered {@link PhoneAccount}s.
+     * Maintains the list of registered {@link android.telecomm.PhoneAccount}s.
      */
     private PhoneAccountRegistrar mPhoneAccountRegistrar;
 
@@ -46,6 +51,8 @@
         mMissedCallNotifier = new MissedCallNotifier(this);
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(this);
 
+        addHangoutsAccount();
+
         if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
             TelecommServiceImpl.init(mMissedCallNotifier, mPhoneAccountRegistrar);
         }
@@ -61,4 +68,29 @@
     MissedCallNotifier getMissedCallNotifier() {
         return mMissedCallNotifier;
     }
+
+    PhoneAccountRegistrar getPhoneAccountRegistrar() {
+        return mPhoneAccountRegistrar;
+    }
+
+    private void addHangoutsAccount() {
+        // TODO: STOPSHIP. We are adding a hacked PhoneAccount to ensure that Wi-Fi calling in
+        // Hangouts continues to work. This needs to be replaced with proper Wi-Fi calling wiring
+        // to the appropriate Connection Services.
+        PhoneAccountMetadata hangouts = new PhoneAccountMetadata(
+                new PhoneAccount(
+                        new ComponentName(
+                                "com.google.android.talk",
+                                "com.google.android.apps.babel.telephony.TeleConnectionService"),
+                        "null_id"),
+                Uri.fromParts("tel", "null_uri", null),
+                PhoneAccountMetadata.CAPABILITY_CALL_PROVIDER,
+                R.drawable.stat_sys_phone_call,
+                "Wi-Fi calling",
+                "Wi-Fi calling by Google Hangouts",
+                false);
+        mPhoneAccountRegistrar.clearAccounts(
+                hangouts.getAccount().getComponentName().getPackageName());
+        mPhoneAccountRegistrar.registerPhoneAccount(hangouts);
+    }
 }
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index fce9c04..a9bbf8e 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -147,38 +147,67 @@
     //
 
     @Override
+    public PhoneAccount getDefaultOutgoingPhoneAccount() {
+        try {
+            return mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount();
+        } catch (Exception e) {
+            Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+            throw e;
+        }
+    }
+
+    @Override
     public List<PhoneAccount> getEnabledPhoneAccounts() {
-        return mPhoneAccountRegistrar.getEnabledAccounts();
+        try {
+            return mPhoneAccountRegistrar.getEnabledPhoneAccounts();
+        } catch (Exception e) {
+            Log.e(this, e, "getEnabledPhoneAccounts");
+            throw e;
+        }
     }
 
     @Override
     public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
-        PhoneAccount registeredAccount = mPhoneAccountRegistrar.getRegisteredAccount(account);
-        if (registeredAccount != null) {
-            return new PhoneAccountMetadata(
-                    registeredAccount, 0, account.getComponentName().getPackageName(), null, false);
+        try {
+            return mPhoneAccountRegistrar.getPhoneAccountMetadata(account);
+        } catch (Exception e) {
+            Log.e(this, e, "getPhoneAccountMetadata %s", account);
+            throw e;
         }
-        return null;
     }
 
     @Override
-    public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
-        enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
-        mPhoneAccountRegistrar.addAccount(account);
-        // TODO(santoscordon): Implement metadata
+    public void registerPhoneAccount(PhoneAccountMetadata metadata) {
+        try {
+            enforceModifyPermissionOrCallingPackage(
+                    metadata.getAccount().getComponentName().getPackageName());
+            mPhoneAccountRegistrar.registerPhoneAccount(metadata);
+        } catch (Exception e) {
+            Log.e(this, e, "registerPhoneAccount %s", metadata);
+            throw e;
+        }
     }
 
     @Override
     public void unregisterPhoneAccount(PhoneAccount account) {
-        enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
-        mPhoneAccountRegistrar.removeAccount(account);
+        try {
+            enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
+            mPhoneAccountRegistrar.unregisterPhoneAccount(account);
+        } catch (Exception e) {
+            Log.e(this, e, "unregisterPhoneAccount %s", account);
+            throw e;
+        }
     }
 
     @Override
     public void clearAccounts(String packageName) {
-        enforceModifyPermissionOrCallingPackage(packageName);
-        // TODO(santoscordon): Is this needed?
-        Log.e(TAG, null, "Unexpected method call: clearAccounts()");
+        try {
+            enforceModifyPermissionOrCallingPackage(packageName);
+            mPhoneAccountRegistrar.clearAccounts(packageName);
+        } catch (Exception e) {
+            Log.e(this, e, "clearAccounts %s", packageName);
+            throw e;
+        }
     }
 
     /**
diff --git a/src/com/android/telecomm/TelephonyUtil.java b/src/com/android/telecomm/TelephonyUtil.java
index ac19525..2d22491 100644
--- a/src/com/android/telecomm/TelephonyUtil.java
+++ b/src/com/android/telecomm/TelephonyUtil.java
@@ -17,9 +17,9 @@
 package com.android.telecomm;
 
 import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.telecomm.PhoneAccount;
+import android.content.Context;
+import android.net.Uri;
+import android.telephony.PhoneNumberUtils;
 
 /**
  * Utilities to deal with the system telephony services. The system telephony services are treated
@@ -31,7 +31,7 @@
     private static final String TELEPHONY_PACKAGE_NAME = "com.android.phone";
 
     private static final String PSTN_CALL_SERVICE_CLASS_NAME =
-            "com.android.services.telephony.PstnConnectionService";
+            "com.android.services.telephony.TelephonyConnectionService";
 
     private TelephonyUtil() {}
 
@@ -41,17 +41,8 @@
         return pstnComponentName.equals(componentName);
     }
 
-    private static void verifyConnectionServiceExists(String serviceName) {
-        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
-        try {
-            ServiceInfo info = packageManager.getServiceInfo(
-                    new ComponentName(TELEPHONY_PACKAGE_NAME, serviceName), 0);
-            if (info == null) {
-                Log.wtf(TAG, "Error, unable to find connection service: %s", serviceName);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.wtf(TAG, e, "Error, exception while trying to find connection service: %s",
-                    serviceName);
-        }
+    static boolean shouldProcessAsEmergency(Context context, Uri handle) {
+        return handle != null && PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+                context, handle.getSchemeSpecificPart());
     }
 }
diff --git a/src/com/android/telecomm/TtyManager.java b/src/com/android/telecomm/TtyManager.java
index c94cd30..61cd6d0 100644
--- a/src/com/android/telecomm/TtyManager.java
+++ b/src/com/android/telecomm/TtyManager.java
@@ -51,7 +51,7 @@
 
     boolean isTtySupported() {
         boolean isEnabled = mContext.getResources().getBoolean(R.bool.tty_enabled);
-        Log.v(this, "isTtySupported: " + isEnabled);
+        Log.v(this, "isTtySupported: %b", isEnabled);
         return isEnabled;
     }
 
diff --git a/tests/res/raw/outgoing_video.mp4 b/tests/res/raw/outgoing_video.mp4
new file mode 100644
index 0000000..3e4f1cb
--- /dev/null
+++ b/tests/res/raw/outgoing_video.mp4
Binary files differ
diff --git a/tests/res/raw/test_video.mp4 b/tests/res/raw/test_video.mp4
new file mode 100644
index 0000000..1a454b3
--- /dev/null
+++ b/tests/res/raw/test_video.mp4
Binary files differ
diff --git a/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java b/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
index 098b065..7002354 100644
--- a/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
+++ b/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountMetadata;
 import android.telecomm.TelecommConstants;
 
 /**
@@ -76,9 +77,7 @@
 
         PhoneAccount phoneAccount = new PhoneAccount(
                 new ComponentName(context, TestConnectionService.class),
-                null /* id */,
-                null /* handle */,
-                PhoneAccount.CAPABILITY_CALL_PROVIDER);
+                null /* id */);
         intent.putExtra(TelecommConstants.EXTRA_PHONE_ACCOUNT, phoneAccount);
 
         // For the purposes of testing, indicate whether the incoming call is a video call by
diff --git a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
index c08f6eb..9724ef8 100644
--- a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
+++ b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
@@ -23,10 +23,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Bundle;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountMetadata;
-import android.telecomm.TelecommConstants;
 import android.telecomm.TelecommManager;
 import android.util.Log;
 import android.widget.Toast;
@@ -88,17 +86,19 @@
      * Registers a phone account with telecomm.
      */
     public void registerPhoneAccount(Context context) {
-        PhoneAccount phoneAccount = new PhoneAccount(
-                new ComponentName(context, TestConnectionService.class),
-                "testapps_TestConnectionService_Account_ID",
+        PhoneAccountMetadata metadata = new PhoneAccountMetadata(
+                new PhoneAccount(
+                        new ComponentName(context, TestConnectionService.class),
+                        "testapps_TestConnectionService_Account_ID"),
                 Uri.parse("tel:555-TEST"),
-                PhoneAccount.CAPABILITY_CALL_PROVIDER);
-        PhoneAccountMetadata metadata = new PhoneAccountMetadata(phoneAccount, 0, null, null,
+                PhoneAccountMetadata.CAPABILITY_CALL_PROVIDER,
+                0,  // iconResId
+                "a label",
+                "a short description",
                 false);
-
         TelecommManager telecommManager =
                 (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
-        telecommManager.registerPhoneAccount(phoneAccount, metadata);
+        telecommManager.registerPhoneAccount(metadata);
     }
 
     /**
diff --git a/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java b/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
index 484871e..ccceedf 100644
--- a/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
+++ b/tests/src/com/android/telecomm/testapps/TestCallVideoProvider.java
@@ -17,6 +17,7 @@
 package com.android.telecomm.testapps;
 
 import android.content.Context;
+import android.media.MediaPlayer;
 import android.os.RemoteException;
 import android.telecomm.CallCameraCapabilities;
 import android.telecomm.CallVideoClient;
@@ -29,6 +30,8 @@
 
 import java.util.Random;
 
+import com.android.telecomm.tests.R;
+
 /**
  * Implements the CallVideoProvider.
  */
@@ -36,9 +39,16 @@
     private RemoteCallVideoClient mCallVideoClient;
     private CallCameraCapabilities mCapabilities;
     private Random random;
-
+    private Surface mDisplaySurface;
+    private Surface mPreviewSurface;
+    private Context mContext;
+    /** Used to play incoming video during a call. */
+    private MediaPlayer mIncomingMediaPlayer;
+    /** Used to play outgoing video during a call. */
+    private MediaPlayer mOutgoingMediaPlayer;
 
     public TestCallVideoProvider(Context context) {
+        mContext = context;
         mCapabilities = new CallCameraCapabilities(false /* zoomSupported */, 0 /* maxZoom */);
         random = new Random();
     }
@@ -58,12 +68,40 @@
 
     @Override
     public void onSetPreviewSurface(Surface surface) {
-        log("Set preview surface");
+        log("Set preview surface " + (surface == null ? "unset" : "set"));
+        mPreviewSurface = surface;
+
+        if (mPreviewSurface != null) {
+            if (mOutgoingMediaPlayer == null) {
+                mOutgoingMediaPlayer = createMediaPlayer(mPreviewSurface, R.raw.outgoing_video);
+            }
+            mOutgoingMediaPlayer.setSurface(mPreviewSurface);
+            if (!mOutgoingMediaPlayer.isPlaying()) {
+                mOutgoingMediaPlayer.start();
+            }
+        } else {
+            mOutgoingMediaPlayer.stop();
+            mOutgoingMediaPlayer.setSurface(null);
+        }
     }
 
     @Override
     public void onSetDisplaySurface(Surface surface) {
-        log("Set display surface");
+        log("Set display surface " + (surface == null ? "unset" : "set"));
+        mDisplaySurface = surface;
+
+        if (mDisplaySurface != null) {
+            if (mIncomingMediaPlayer == null) {
+                mIncomingMediaPlayer = createMediaPlayer(mDisplaySurface, R.raw.test_video);
+            }
+            mIncomingMediaPlayer.setSurface(mDisplaySurface);
+            if (!mIncomingMediaPlayer.isPlaying()) {
+                mIncomingMediaPlayer.start();
+            }
+        } else {
+            mIncomingMediaPlayer.stop();
+            mIncomingMediaPlayer.setSurface(null);
+        }
     }
 
     @Override
@@ -151,4 +189,18 @@
     private static void log(String msg) {
         Log.w("TestCallVideoProvider", "[TestCallServiceProvider] " + msg);
     }
+
+    /**
+     * Creates a media player to play a video resource on a surface.
+     * @param surface The surface.
+     * @param videoResource The video resource.
+     * @return The {@code MediaPlayer}.
+     */
+    private MediaPlayer createMediaPlayer(Surface surface, int videoResource) {
+        MediaPlayer mediaPlayer = MediaPlayer.create(mContext.getApplicationContext(),
+                videoResource);
+        mediaPlayer.setSurface(surface);
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
 }
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionService.java b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
index a80109c..2178af0 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionService.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
@@ -16,6 +16,7 @@
 
 package com.android.telecomm.testapps;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.media.MediaPlayer;
@@ -43,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Random;
 
 /**
  * Service which provides fake calls to test the ConnectionService interface.
@@ -60,6 +62,11 @@
      */
     public static final String IS_VIDEO_CALL = "IsVideoCall";
 
+    /**
+     * Random number generator used to generate phone numbers.
+     */
+    private Random mRandom = new Random();
+
     private final class TestConnection extends Connection {
         private final RemoteConnection.Listener mProxyListener = new RemoteConnection.Listener() {
             @Override
@@ -117,6 +124,12 @@
             }
 
             @Override
+            public void onStartActivityFromInCall(
+                    RemoteConnection connection, PendingIntent intent) {
+                startActivityFromInCall(intent);
+            }
+
+            @Override
             public void onDestroyed(RemoteConnection connection) {
                 setDestroyed();
             }
@@ -425,13 +438,13 @@
         PhoneAccount account = request.getAccount();
         ComponentName componentName = new ComponentName(this, TestConnectionService.class);
         if (account != null && componentName.equals(account.getComponentName())) {
-            // Use dummy number for testing incoming calls.
-            Uri handle = Uri.fromParts(SCHEME_TEL, "5551234", null);
-
             // Get the stashed intent extra that determines if this is a video call or audio call.
             Bundle extras = request.getExtras();
             boolean isVideoCall = extras.getBoolean(IS_VIDEO_CALL);
 
+            // Use dummy number for testing incoming calls.
+            Uri handle = Uri.fromParts(SCHEME_TEL, getDummyNumber(isVideoCall), null);
+
             TestConnection connection = new TestConnection(null, Connection.State.RINGING);
             if (isVideoCall) {
                 connection.setCallVideoProvider(new TestCallVideoProvider(getApplicationContext()));
@@ -469,4 +482,18 @@
             lookupRemoteAccounts(request.getHandle(), accountResponse);
         }
     }
+
+    /**
+     * Generates a random phone number of format 555YXXX.  Where Y will be {@code 1} if the
+     * phone number is for a video call and {@code 0} for an audio call.  XXX is a randomly
+     * generated phone number.
+     *
+     * @param isVideo {@code True} if the call is a video call.
+     * @return The phone number.
+     */
+    private String getDummyNumber(boolean isVideo) {
+        int videoDigit = isVideo ? 1 : 0;
+        int number = mRandom.nextInt(999);
+        return String.format("555%s%03d", videoDigit, number);
+    }
 }