Merge "Tweak VVM error handling" into nyc-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4a95259..26b0c7a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -337,7 +337,7 @@
              non-voice-capable tablets and regular phone devices. -->
         <activity android:name="MobileNetworkSettings"
             android:label="@string/settings_label"
-            android:theme="@style/SettingsLight">
+            android:theme="@style/NetworkOperatorsSettingsTheme">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.MAIN" />
@@ -664,14 +664,16 @@
             </intent-filter>
         </provider>
         <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.VOICEMAIL_SMS_RECEIVED"/>
             </intent-filter>
         </receiver>
         <receiver
             android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
                 <action android:name="android.intent.action.SIM_STATE_CHANGED" />
@@ -680,7 +682,8 @@
         <receiver
             android:name="com.android.phone.vvm.omtp.VvmBootCompletedReceiver"
             android:exported="true"
-            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
+            android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
@@ -688,7 +691,8 @@
         <receiver
             android:name="com.android.phone.vvm.omtp.fetch.FetchVoicemailReceiver"
             android:exported="true"
-            android:permission="com.android.voicemail.permission.READ_VOICEMAIL">
+            android:permission="com.android.voicemail.permission.READ_VOICEMAIL"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.FETCH_VOICEMAIL" />
                     <data
@@ -700,14 +704,16 @@
         <receiver
             android:name="com.android.phone.vvm.omtp.sync.OmtpVvmSyncReceiver"
             android:exported="true"
-            android:permission="com.android.voicemail.permission.READ_VOICEMAIL">
+            android:permission="com.android.voicemail.permission.READ_VOICEMAIL"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.provider.action.SYNC_VOICEMAIL"/>
             </intent-filter>
         </receiver>
         <receiver
             android:name="com.android.phone.vvm.omtp.sync.VoicemailProviderChangeReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.PROVIDER_CHANGED" />
                 <data
@@ -724,7 +730,8 @@
             android:name="com.android.phone.vvm.omtp.sms.OmtpProvisioningService"
             android:exported="false" />
 
-        <receiver android:name="com.android.phone.vvm.omtp.VvmPackageInstallReceiver">
+        <receiver android:name="com.android.phone.vvm.omtp.VvmPackageInstallReceiver"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_INSTALL" />
                 <action android:name="android.intent.action.PACKAGE_ADDED" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 67572ed..870d692 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -54,9 +54,4 @@
 
     <color name="dialer_dialpad_touch_tint">#330288d1</color>
     <color name="floating_action_button_touch_tint">#80ffffff</color>
-
-    <color name="network_operators_color_primary">#ff263238</color>
-    <color name="network_operators_color_primary_dark">#ff21272b</color>
-
-    <color name="emergency_dialer_background">#ff263238</color>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9d2d47f..057352d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -153,13 +153,6 @@
         <item name="android:layout_marginEnd">5dip</item>
     </style>
 
-    <!-- Theme for the activity com.android.phone.Settings, which is the
-         "Mobile network settings" screen (used on non-voice-capable
-         tablets as well as regular phone devices.) -->
-    <style name="Theme.Settings" parent="@android:style/Theme.Holo.DialogWhenLarge">
-        <item name="android:windowCloseOnTouchOutside">true</item>
-    </style>
-
     <style name="SettingsLight" parent="android:Theme.Material.Light">
         <item name="android:windowBackground">@color/phone_settings_background_color</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -186,11 +179,7 @@
         <item name="android:textColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
     </style>
 
-    <style name="NetworkOperatorsSettingsTheme" parent="@android:style/Theme.Material.Light">
-        <item name="android:actionBarTheme">@android:style/ThemeOverlay.Material.Dark.ActionBar</item>
-        <item name="android:colorPrimary">@color/network_operators_color_primary</item>
-        <item name="android:colorPrimaryDark">@color/network_operators_color_primary_dark</item>
-    </style>
+    <style name="NetworkOperatorsSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings" />
 
     <style name="Empty" parent="@android:style/Theme.Material.Light">
         <item name="android:windowIsTranslucent">true</item>
@@ -256,10 +245,9 @@
         <item name="android:src">@drawable/overflow_menu</item>
     </style>
 
-    <style name="EmergencyDialerTheme" parent="@android:style/Theme.Material.NoActionBar">
-        <item name="android:colorPrimary">@color/emergency_dialer_background</item>
-        <item name="android:colorPrimaryDark">@color/emergency_dialer_background</item>
-        <item name="android:windowBackground">@color/emergency_dialer_background</item>
+    <style name="EmergencyDialerTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dark.NoActionBar">
+        <item name="android:colorPrimaryDark">?android:attr/colorPrimary</item>
+        <item name="android:windowBackground">?android:attr/colorPrimary</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
@@ -296,8 +284,4 @@
         <item name="android:backgroundDimEnabled">false</item>
     </style>
 
-    <style name="Theme.Material.Settings" parent="@android:style/Theme.Material.Settings">
-        <item name="@*android:actionBarSize">56dip</item>
-        <item name="preferenceBackgroundColor">@drawable/preference_background</item>
-    </style>
 </resources>
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index 404c976..fd4815e 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -421,7 +421,6 @@
     @Override
     protected void onCreate(Bundle icicle) {
         if (DBG) log("onCreate:+");
-        setTheme(R.style.Theme_Material_Settings);
         super.onCreate(icicle);
 
         mHandler = new MyHandler();
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 783878c..808a5d6 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -637,8 +637,53 @@
         notifier.updateCallNotifierRegistrationsAfterRadioTechnologyChange();
     }
 
-    private void handleAirplaneModeChange(int newMode) {
-        if (newMode == AIRPLANE_ON) {
+    private void handleAirplaneModeChange(Context context, int newMode) {
+        int cellState = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
+        boolean isAirplaneNewlyOn = (newMode == 1);
+        switch (cellState) {
+            case PhoneConstants.CELL_OFF_FLAG:
+                // Airplane mode does not affect the cell radio if user
+                // has turned it off.
+                break;
+            case PhoneConstants.CELL_ON_FLAG:
+                maybeTurnCellOff(context, isAirplaneNewlyOn);
+                break;
+            case PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG:
+                maybeTurnCellOn(context, isAirplaneNewlyOn);
+                break;
+        }
+    }
+
+    /*
+     * Returns true if the radio must be turned off when entering airplane mode.
+     */
+    private boolean isCellOffInAirplaneMode(Context context) {
+        String airplaneModeRadios = Settings.Global.getString(context.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_RADIOS);
+        return airplaneModeRadios == null
+                || airplaneModeRadios.contains(Settings.Global.RADIO_CELL);
+    }
+
+    private void setRadioPowerOff(Context context) {
+        Log.i(LOG_TAG, "Turning radio off - airplane");
+        Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
+                 PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT, 0);
+        PhoneUtils.setRadioPower(false);
+    }
+
+    private void setRadioPowerOn(Context context) {
+        Log.i(LOG_TAG, "Turning radio on - airplane");
+        Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
+                PhoneConstants.CELL_ON_FLAG);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+                1);
+        PhoneUtils.setRadioPower(true);
+    }
+
+    private void maybeTurnCellOff(Context context, boolean isAirplaneNewlyOn) {
+        if (isAirplaneNewlyOn) {
             // If we are trying to turn off the radio, make sure there are no active
             // emergency calls.  If there are, switch airplane mode back to off.
             if (PhoneUtils.isInEmergencyCall(mCM)) {
@@ -647,13 +692,17 @@
                 Toast.makeText(this, R.string.radio_off_during_emergency_call, Toast.LENGTH_LONG)
                         .show();
                 Log.i(LOG_TAG, "Ignoring airplane mode: emergency call. Turning airplane off");
+            } else if (isCellOffInAirplaneMode(context)) {
+                setRadioPowerOff(context);
             } else {
-                Log.i(LOG_TAG, "Turning radio off - airplane");
-                PhoneUtils.setRadioPower(false);
+                Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
             }
-        } else {
-            Log.i(LOG_TAG, "Turning radio on - airplane");
-            PhoneUtils.setRadioPower(true);
+        }
+    }
+
+    private void maybeTurnCellOn(Context context, boolean isAirplaneNewlyOn) {
+        if (!isAirplaneNewlyOn) {
+            setRadioPowerOn(context);
         }
     }
 
@@ -671,7 +720,7 @@
                 if (airplaneMode != AIRPLANE_OFF) {
                     airplaneMode = AIRPLANE_ON;
                 }
-                handleAirplaneModeChange(airplaneMode);
+                handleAirplaneModeChange(context, airplaneMode);
             } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index ed014d3..546f0f2 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -3297,6 +3297,15 @@
      */
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mPhone.getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            writer.println("Permission Denial: can't dump Phone from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + "without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
         DumpsysHandler.dump(mPhone.getContext(), fd, writer, args);
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 3a6b7d3..f22711a 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -21,7 +21,6 @@
 import android.content.pm.IPackageManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
@@ -49,11 +48,6 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (UserHandle.myUserId() != UserHandle.USER_SYSTEM) {
-            VvmLog.v(TAG, "Received broadcast for user that is not system.");
-            return;
-        }
-
         final String action = intent.getAction();
         if (action == null) {
             VvmLog.w(TAG, "Null action for intent.");
diff --git a/src/com/android/services/telephony/EmergencyCallHelper.java b/src/com/android/services/telephony/EmergencyCallHelper.java
index c64a649..295f4f7 100644
--- a/src/com/android/services/telephony/EmergencyCallHelper.java
+++ b/src/com/android/services/telephony/EmergencyCallHelper.java
@@ -17,18 +17,17 @@
 package com.android.services.telephony;
 
 import android.content.Context;
-
 import android.content.Intent;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 
-import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
 
 /**
  * Helper class that implements special behavior related to emergency calls. Specifically, this
@@ -36,220 +35,75 @@
  * (i.e. the device is in airplane mode), by forcibly turning the radio back on, waiting for it to
  * come up, and then retrying the emergency call.
  */
-public class EmergencyCallHelper {
-
-    /**
-     * Receives the result of the EmergencyCallHelper's attempt to turn on the radio.
-     */
-    interface Callback {
-        void onComplete(boolean isRadioReady);
-    }
-
-    // Number of times to retry the call, and time between retry attempts.
-    public static final int MAX_NUM_RETRIES = 5;
-    public static final long TIME_BETWEEN_RETRIES_MILLIS = 5000;  // msec
-
-    // Handler message codes; see handleMessage()
-    private static final int MSG_START_SEQUENCE = 1;
-    private static final int MSG_SERVICE_STATE_CHANGED = 2;
-    private static final int MSG_RETRY_TIMEOUT = 3;
+public class EmergencyCallHelper implements EmergencyCallStateListener.Callback {
 
     private final Context mContext;
+    private EmergencyCallStateListener.Callback mCallback;
+    private List<EmergencyCallStateListener> mListeners;
+    private List<EmergencyCallStateListener> mInProgressListeners;
+    private boolean mIsEmergencyCallingEnabled;
 
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START_SEQUENCE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    Phone phone = (Phone) args.arg1;
-                    EmergencyCallHelper.Callback callback =
-                            (EmergencyCallHelper.Callback) args.arg2;
-                    args.recycle();
-
-                    startSequenceInternal(phone, callback);
-                    break;
-                case MSG_SERVICE_STATE_CHANGED:
-                    onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
-                    break;
-                case MSG_RETRY_TIMEOUT:
-                    onRetryTimeout();
-                    break;
-                default:
-                    Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what);
-                    break;
-            }
-        }
-    };
-
-
-    private Callback mCallback;  // The callback to notify upon completion.
-    private Phone mPhone;  // The phone that will attempt to place the call.
-    private int mNumRetriesSoFar;
 
     public EmergencyCallHelper(Context context) {
-        Log.d(this, "EmergencyCallHelper constructor.");
         mContext = context;
+        mInProgressListeners = new ArrayList<>(2);
     }
 
+    private void setupListeners() {
+        if (mListeners != null) {
+            return;
+        }
+        mListeners = new ArrayList<>(2);
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            mListeners.add(new EmergencyCallStateListener());
+        }
+    }
     /**
      * Starts the "turn on radio" sequence. This is the (single) external API of the
      * EmergencyCallHelper class.
      *
      * This method kicks off the following sequence:
-     * - Power on the radio.
+     * - Power on the radio for each Phone
      * - Listen for the service state change event telling us the radio has come up.
-     * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
-     *   radio.
+     * - Retry if we've gone a significant amount of time without any response from the radio.
      * - Finally, clean up any leftover state.
      *
      * This method is safe to call from any thread, since it simply posts a message to the
      * EmergencyCallHelper's handler (thus ensuring that the rest of the sequence is entirely
-     * serialized, and runs only on the handler thread.)
+     * serialized, and runs on the main looper.)
      */
-    public void startTurnOnRadioSequence(Phone phone, Callback callback) {
-        Log.d(this, "startTurnOnRadioSequence");
-
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = phone;
-        args.arg2 = callback;
-        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
-    }
-
-    /**
-     * Actual implementation of startTurnOnRadioSequence(), guaranteed to run on the handler thread.
-     * @see #startTurnOnRadioSequence
-     */
-    private void startSequenceInternal(Phone phone, Callback callback) {
-        Log.d(this, "startSequenceInternal()");
-
-        // First of all, clean up any state left over from a prior emergency call sequence. This
-        // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
-        // we're already in the middle of the sequence.
-        cleanup();
-
-        mPhone = phone;
+    public void enableEmergencyCalling(EmergencyCallStateListener.Callback callback) {
+        setupListeners();
         mCallback = callback;
+        mInProgressListeners.clear();
+        mIsEmergencyCallingEnabled = false;
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            Phone phone = PhoneFactory.getPhone(i);
+            if (phone == null)
+                continue;
 
-
-        // No need to check the current service state here, since the only reason to invoke this
-        // method in the first place is if the radio is powered-off. So just go ahead and turn the
-        // radio on.
-
-        powerOnRadio();  // We'll get an onServiceStateChanged() callback
-                         // when the radio successfully comes up.
-
-        // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see
-        // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
-        // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
-        startRetryTimer();
-    }
-
-    /**
-     * Handles the SERVICE_STATE_CHANGED event. Normally this event tells us that the radio has
-     * finally come up. In that case, it's now safe to actually place the emergency call.
-     */
-    private void onServiceStateChanged(ServiceState state) {
-        Log.d(this, "onServiceStateChanged(), new state = %s.", state);
-
-        // Possible service states:
-        // - STATE_IN_SERVICE        // Normal operation
-        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
-        //                           // or no radio signal
-        // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
-        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
-
-        if (isOkToCall(state.getState(), mPhone.getState())) {
-            // Woo hoo!  It's OK to actually place the call.
-            Log.d(this, "onServiceStateChanged: ok to call!");
-
-            onComplete(true);
-            cleanup();
-        } else {
-            // The service state changed, but we're still not ready to call yet. (This probably was
-            // the transition from STATE_POWER_OFF to STATE_OUT_OF_SERVICE, which happens
-            // immediately after powering-on the radio.)
-            //
-            // So just keep waiting; we'll probably get to either STATE_IN_SERVICE or
-            // STATE_EMERGENCY_ONLY very shortly. (Or even if that doesn't happen, we'll at least do
-            // another retry when the RETRY_TIMEOUT event fires.)
-            Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting.");
+            mInProgressListeners.add(mListeners.get(i));
+            mListeners.get(i).waitForRadioOn(phone, this);
         }
+
+        powerOnRadio();
     }
-
-    private boolean isOkToCall(int serviceState, PhoneConstants.State phoneState) {
-        // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY, it's finally OK to place
-        // the emergency call.
-        return ((phoneState == PhoneConstants.State.OFFHOOK)
-                || (serviceState == ServiceState.STATE_IN_SERVICE)
-                || (serviceState == ServiceState.STATE_EMERGENCY_ONLY)) ||
-
-                // Allow STATE_OUT_OF_SERVICE if we are at the max number of retries.
-                (mNumRetriesSoFar == MAX_NUM_RETRIES &&
-                 serviceState == ServiceState.STATE_OUT_OF_SERVICE);
-    }
-
     /**
-     * Handles the retry timer expiring.
-     */
-    private void onRetryTimeout() {
-        PhoneConstants.State phoneState = mPhone.getState();
-        int serviceState = mPhone.getServiceState().getState();
-        Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
-               phoneState, serviceState, mNumRetriesSoFar);
-
-        // - If we're actually in a call, we've succeeded.
-        // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
-        //   but somehow didn't get the service state change event.  In that case, try to place the
-        //   call.
-        // - If the radio is still powered off, try powering it on again.
-
-        if (isOkToCall(serviceState, phoneState)) {
-            Log.d(this, "onRetryTimeout: Radio is on. Cleaning up.");
-
-            // Woo hoo -- we successfully got out of airplane mode.
-            onComplete(true);
-            cleanup();
-        } else {
-            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
-            // powered-on.  Try again.
-
-            mNumRetriesSoFar++;
-            Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
-
-            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
-                Log.w(this, "Hit MAX_NUM_RETRIES; giving up.");
-                cleanup();
-            } else {
-                Log.d(this, "Trying (again) to turn on the radio.");
-                powerOnRadio();  // Again, we'll (hopefully) get an onServiceStateChanged() callback
-                                 // when the radio successfully comes up.
-                startRetryTimer();
-            }
-        }
-    }
-
-    /**
-     * Attempt to power on the radio (i.e. take the device out of airplane mode.)
-     * Additionally, start listening for service state changes; we'll eventually get an
-     * onServiceStateChanged() callback when the radio successfully comes up.
+     * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
+     * get an onServiceStateChanged() callback when the radio successfully comes up.
      */
     private void powerOnRadio() {
         Log.d(this, "powerOnRadio().");
 
-        // We're about to turn on the radio, so arrange to be notified when the sequence is
-        // complete.
-        registerForServiceStateChanged();
-
         // If airplane mode is on, we turn it off the same way that the Settings activity turns it
         // off.
         if (Settings.Global.getInt(mContext.getContentResolver(),
-                                   Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
+                Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
             Log.d(this, "==> Turning off airplane mode.");
 
             // Change the system setting
             Settings.Global.putInt(mContext.getContentResolver(),
-                                   Settings.Global.AIRPLANE_MODE_ON, 0);
+                    Settings.Global.AIRPLANE_MODE_ON, 0);
 
             // Post the broadcast intend for change in airplane mode
             // TODO: We really should not be in charge of sending this broadcast.
@@ -258,77 +112,19 @@
             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
             intent.putExtra("state", false);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-        } else {
-            // Otherwise, for some strange reason the radio is off (even though the Settings
-            // database doesn't think we're in airplane mode.)  In this case just turn the radio
-            // back on.
-            Log.d(this, "==> (Apparently) not in airplane mode; manually powering radio on.");
-            mPhone.setRadioPower(true);
         }
     }
 
     /**
-     * Clean up when done with the whole sequence: either after successfully turning on the radio,
-     * or after bailing out because of too many failures.
-     *
-     * The exact cleanup steps are:
-     * - Notify callback if we still hadn't sent it a response.
-     * - Double-check that we're not still registered for any telephony events
-     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
-     *
-     * Basically this method guarantees that there will be no more activity from the
-     * EmergencyCallHelper until someone kicks off the whole sequence again with another call to
-     * {@link #startTurnOnRadioSequence}
-     *
-     * TODO: Do the work for the comment below:
-     * Note we don't call this method simply after a successful call to placeCall(), since it's
-     * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error.
+     * This method is called from multiple Listeners on the Main Looper.
+     * Synchronization is not necessary.
      */
-    private void cleanup() {
-        Log.d(this, "cleanup()");
-
-        // This will send a failure call back if callback has yet to be invoked.  If the callback
-        // was already invoked, it's a no-op.
-        onComplete(false);
-
-        unregisterForServiceStateChanged();
-        cancelRetryTimer();
-
-        // Used for unregisterForServiceStateChanged() so we null it out here instead.
-        mPhone = null;
-        mNumRetriesSoFar = 0;
-    }
-
-    private void startRetryTimer() {
-        cancelRetryTimer();
-        mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
-    }
-
-    private void cancelRetryTimer() {
-        mHandler.removeMessages(MSG_RETRY_TIMEOUT);
-    }
-
-    private void registerForServiceStateChanged() {
-        // Unregister first, just to make sure we never register ourselves twice.  (We need this
-        // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
-        // the same handler.)
-        unregisterForServiceStateChanged();
-        mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
-    }
-
-    private void unregisterForServiceStateChanged() {
-        // This method is safe to call even if we haven't set mPhone yet.
-        if (mPhone != null) {
-            mPhone.unregisterForServiceStateChanged(mHandler);  // Safe even if unnecessary
-        }
-        mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED);  // Clean up any pending messages too
-    }
-
-    private void onComplete(boolean isRadioReady) {
-        if (mCallback != null) {
-            Callback tempCallback = mCallback;
-            mCallback = null;
-            tempCallback.onComplete(isRadioReady);
+    @Override
+    public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
+        mIsEmergencyCallingEnabled |= isRadioReady;
+        mInProgressListeners.remove(listener);
+        if (mCallback != null && mInProgressListeners.isEmpty()) {
+            mCallback.onComplete(null, mIsEmergencyCallingEnabled);
         }
     }
 }
diff --git a/src/com/android/services/telephony/EmergencyCallStateListener.java b/src/com/android/services/telephony/EmergencyCallStateListener.java
new file mode 100644
index 0000000..2346a7f
--- /dev/null
+++ b/src/com/android/services/telephony/EmergencyCallStateListener.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2016 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.services.telephony;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ServiceState;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SubscriptionController;
+
+/**
+ * Helper class that listens to a Phone's radio state and sends a callback when the radio state of
+ * that Phone is either "in service" or "emergency calls only."
+ */
+public class EmergencyCallStateListener {
+
+    /**
+     * Receives the result of the EmergencyCallStateListener's attempt to turn on the radio.
+     */
+    interface Callback {
+        void onComplete(EmergencyCallStateListener listener, boolean isRadioReady);
+    }
+
+    // Number of times to retry the call, and time between retry attempts.
+    private static int MAX_NUM_RETRIES = 5;
+    private static long TIME_BETWEEN_RETRIES_MILLIS = 5000;  // msec
+
+    // Handler message codes; see handleMessage()
+    @VisibleForTesting
+    public static final int MSG_START_SEQUENCE = 1;
+    @VisibleForTesting
+    public static final int MSG_SERVICE_STATE_CHANGED = 2;
+    @VisibleForTesting
+    public static final int MSG_RETRY_TIMEOUT = 3;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_SEQUENCE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Phone phone = (Phone) args.arg1;
+                        EmergencyCallStateListener.Callback callback =
+                                (EmergencyCallStateListener.Callback) args.arg2;
+                        startSequenceInternal(phone, callback);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                case MSG_SERVICE_STATE_CHANGED:
+                    onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
+                    break;
+                case MSG_RETRY_TIMEOUT:
+                    onRetryTimeout();
+                    break;
+                default:
+                    Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what);
+                    break;
+            }
+        }
+    };
+
+
+    private Callback mCallback;  // The callback to notify upon completion.
+    private Phone mPhone;  // The phone that will attempt to place the call.
+    private int mNumRetriesSoFar;
+
+    /**
+     * Starts the "wait for radio" sequence. This is the (single) external API of the
+     * EmergencyCallStateListener class.
+     *
+     * This method kicks off the following sequence:
+     * - Listen for the service state change event telling us the radio has come up.
+     * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
+     *   radio.
+     * - Finally, clean up any leftover state.
+     *
+     * This method is safe to call from any thread, since it simply posts a message to the
+     * EmergencyCallStateListener's handler (thus ensuring that the rest of the sequence is entirely
+     * serialized, and runs only on the handler thread.)
+     */
+    public void waitForRadioOn(Phone phone, Callback callback) {
+        Log.d(this, "waitForRadioOn: Phone " + phone.getPhoneId());
+
+        if (mPhone != null) {
+            // If there already is an ongoing request, ignore the new one!
+            return;
+        }
+
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = phone;
+        args.arg2 = callback;
+        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
+    }
+
+    /**
+     * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread.
+     *
+     * @see #waitForRadioOn
+     */
+    private void startSequenceInternal(Phone phone, Callback callback) {
+        Log.d(this, "startSequenceInternal: Phone " + phone.getPhoneId());
+
+        // First of all, clean up any state left over from a prior emergency call sequence. This
+        // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
+        // we're already in the middle of the sequence.
+        cleanup();
+
+        mPhone = phone;
+        mCallback = callback;
+
+        registerForServiceStateChanged();
+        // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see
+        // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
+        // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
+        startRetryTimer();
+    }
+
+    /**
+     * Handles the SERVICE_STATE_CHANGED event. Normally this event tells us that the radio has
+     * finally come up. In that case, it's now safe to actually place the emergency call.
+     */
+    private void onServiceStateChanged(ServiceState state) {
+        Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state,
+                mPhone.getPhoneId());
+
+        // Possible service states:
+        // - STATE_IN_SERVICE        // Normal operation
+        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
+        //                           // or no radio signal
+        // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
+        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
+
+        if (isOkToCall(state.getState())) {
+            // Woo hoo!  It's OK to actually place the call.
+            Log.d(this, "onServiceStateChanged: ok to call!");
+
+            onComplete(true);
+            cleanup();
+        } else {
+            // The service state changed, but we're still not ready to call yet. (This probably was
+            // the transition from STATE_POWER_OFF to STATE_OUT_OF_SERVICE, which happens
+            // immediately after powering-on the radio.)
+            //
+            // So just keep waiting; we'll probably get to either STATE_IN_SERVICE or
+            // STATE_EMERGENCY_ONLY very shortly. (Or even if that doesn't happen, we'll at least do
+            // another retry when the RETRY_TIMEOUT event fires.)
+            Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting.");
+        }
+    }
+
+    private boolean isOkToCall(int serviceState) {
+        // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY, it's finally OK to place
+        // the emergency call.
+        return ((mPhone.getState() == PhoneConstants.State.OFFHOOK)
+                || (serviceState == ServiceState.STATE_IN_SERVICE)
+                || (serviceState == ServiceState.STATE_EMERGENCY_ONLY))
+                // STATE_EMERGENCY_ONLY currently is not used, so we must also check the service
+                // state for emergency only calling.
+                || (serviceState == ServiceState.STATE_OUT_OF_SERVICE &&
+                        mPhone.getServiceState().isEmergencyOnly())
+                // Allow STATE_OUT_OF_SERVICE if we are at the max number of retries.
+                || (mNumRetriesSoFar == MAX_NUM_RETRIES &&
+                        serviceState == ServiceState.STATE_OUT_OF_SERVICE);
+    }
+
+    /**
+     * Handles the retry timer expiring.
+     */
+    private void onRetryTimeout() {
+        int serviceState = mPhone.getServiceState().getState();
+        Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
+                mPhone.getState(), serviceState, mNumRetriesSoFar);
+
+        // - If we're actually in a call, we've succeeded.
+        // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
+        //   but somehow didn't get the service state change event.  In that case, try to place the
+        //   call.
+        // - If the radio is still powered off, try powering it on again.
+
+        if (isOkToCall(serviceState)) {
+            Log.d(this, "onRetryTimeout: Radio is on. Cleaning up.");
+
+            // Woo hoo -- we successfully got out of airplane mode.
+            onComplete(true);
+            cleanup();
+        } else {
+            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
+            // powered-on.  Try again.
+
+            mNumRetriesSoFar++;
+            Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
+
+            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
+                Log.w(this, "Hit MAX_NUM_RETRIES; giving up.");
+                cleanup();
+            } else {
+                Log.d(this, "Trying (again) to turn on the radio.");
+                mPhone.setRadioPower(true);
+                startRetryTimer();
+            }
+        }
+    }
+
+    /**
+     * Clean up when done with the whole sequence: either after successfully turning on the radio,
+     * or after bailing out because of too many failures.
+     *
+     * The exact cleanup steps are:
+     * - Notify callback if we still hadn't sent it a response.
+     * - Double-check that we're not still registered for any telephony events
+     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
+     *
+     * Basically this method guarantees that there will be no more activity from the
+     * EmergencyCallStateListener until someone kicks off the whole sequence again with another call
+     * to {@link #waitForRadioOn}
+     *
+     * TODO: Do the work for the comment below:
+     * Note we don't call this method simply after a successful call to placeCall(), since it's
+     * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error.
+     */
+    private void cleanup() {
+        Log.d(this, "cleanup()");
+
+        // This will send a failure call back if callback has yet to be invoked.  If the callback
+        // was already invoked, it's a no-op.
+        onComplete(false);
+
+        unregisterForServiceStateChanged();
+        cancelRetryTimer();
+
+        // Used for unregisterForServiceStateChanged() so we null it out here instead.
+        mPhone = null;
+        mNumRetriesSoFar = 0;
+    }
+
+    private void startRetryTimer() {
+        cancelRetryTimer();
+        mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
+    }
+
+    private void cancelRetryTimer() {
+        mHandler.removeMessages(MSG_RETRY_TIMEOUT);
+    }
+
+    private void registerForServiceStateChanged() {
+        // Unregister first, just to make sure we never register ourselves twice.  (We need this
+        // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
+        // the same handler.)
+        unregisterForServiceStateChanged();
+        mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
+    }
+
+    private void unregisterForServiceStateChanged() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.unregisterForServiceStateChanged(mHandler);  // Safe even if unnecessary
+        }
+        mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED);  // Clean up any pending messages too
+    }
+
+    private void onComplete(boolean isRadioReady) {
+        if (mCallback != null) {
+            Callback tempCallback = mCallback;
+            mCallback = null;
+            tempCallback.onComplete(this, isRadioReady);
+        }
+    }
+
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    public void setMaxNumRetries(int retries) {
+        MAX_NUM_RETRIES = retries;
+    }
+
+    @VisibleForTesting
+    public void setTimeBetweenRetriesMillis(long timeMs) {
+        TIME_BETWEEN_RETRIES_MILLIS = timeMs;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !getClass().equals(o.getClass())) return false;
+
+        EmergencyCallStateListener that = (EmergencyCallStateListener) o;
+
+        if (mNumRetriesSoFar != that.mNumRetriesSoFar) {
+            return false;
+        }
+        if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) {
+            return false;
+        }
+        return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null;
+
+    }
+}
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 69d57d7..62bbfe2 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -808,13 +808,19 @@
             }
 
             PhoneAccountHandle phoneAccountHandle = null;
-            if (mConferenceHost.getPhone() != null &&
-                    mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
-                Phone imsPhone = mConferenceHost.getPhone();
-                // The phone account handle for an ImsPhone is based on the default phone (ie the
-                // base GSM or CDMA phone, not on the ImsPhone itself).
-                phoneAccountHandle =
-                        PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
+            if (mConferenceHost.getPhone() != null) {
+                if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
+                    Phone imsPhone = mConferenceHost.getPhone();
+                    // The phone account handle for an ImsPhone is based on the default phone (ie the
+                    // base GSM or CDMA phone, not on the ImsPhone itself).
+                    phoneAccountHandle =
+                            PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
+                } else {
+                    // In the case of SRVCC, we still need a phone account, so use the top level phone
+                    // to create a phone account.
+                    phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
+                            mConferenceHost.getPhone());
+                }
             }
 
             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 0f9ae5d..a874674 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -78,6 +78,12 @@
             Log.v(this, "onConferenceStarted");
             recalculate();
         }
+
+        @Override
+        public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {
+            Log.v(this, "onConferenceSupportedChanged");
+            recalculate();
+        }
     };
 
     /**
@@ -172,6 +178,7 @@
             // If this connection does not support being in a conference call, then it is not
             // conferenceable with any other connection.
             if (!connection.isConferenceSupported()) {
+                connection.setConferenceableConnections(Collections.<Connection>emptyList());
                 continue;
             }
 
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 19b1d8a..b5b23b4 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -27,7 +27,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.os.ServiceManager;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -40,7 +39,6 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.IPhoneSubInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.phone.PhoneGlobals;
@@ -50,6 +48,7 @@
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Owns all data we have registered with Telecom including handling dynamic addition and
@@ -72,6 +71,7 @@
         private boolean mIsVideoPauseSupported;
         private boolean mIsMergeCallSupported;
         private boolean mIsVideoConferencingSupported;
+        private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
@@ -191,6 +191,8 @@
             }
             mIsMergeCallSupported = isCarrierMergeCallSupported();
             mIsVideoConferencingSupported = isCarrierVideoConferencingSupported();
+            mIsMergeOfWifiCallsAllowedWhenVoWifiOff =
+                    isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff();
 
             if (isEmergency && mContext.getResources().getBoolean(
                     R.bool.config_emergency_account_emergency_calls_only)) {
@@ -312,6 +314,20 @@
         }
 
         /**
+         * Determines from carrier config whether merging of wifi calls is allowed when VoWIFI is
+         * turned off.
+         *
+         * @return {@code true} merging of wifi calls when VoWIFI is disabled should be prevented,
+         *      {@code false} otherwise.
+         */
+        private boolean isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return b != null && b.getBoolean(
+                    CarrierConfigManager.KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL);
+        }
+
+        /**
          * @return The {@linke PhoneAccount} extras associated with the current subscription.
          */
         private Bundle getPhoneAccountExtras() {
@@ -366,6 +382,14 @@
         public boolean isVideoConferencingSupported() {
             return mIsVideoConferencingSupported;
         }
+
+        /**
+         * Indicate whether this account allow merging of wifi calls when VoWIFI is off.
+         * @return {@code true} if allowed, {@code false} otherwise.
+         */
+        public boolean isMergeOfWifiCallsAllowedWhenVoWifiOff() {
+            return mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
+        }
     }
 
     private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -396,6 +420,7 @@
     private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
+    private Object mAccountsLock = new Object();
     private int mServiceState = ServiceState.STATE_POWER_OFF;
 
     // TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
@@ -432,9 +457,11 @@
      * @return {@code True} if video pausing is supported.
      */
     boolean isVideoPauseSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isVideoPauseSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isVideoPauseSupported();
+                }
             }
         }
         return false;
@@ -448,9 +475,11 @@
      * @return {@code True} if merging calls is supported.
      */
     boolean isMergeCallSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isMergeCallSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isMergeCallSupported();
+                }
             }
         }
         return false;
@@ -464,15 +493,37 @@
      * @return {@code True} if video conferencing is supported.
      */
     boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isVideoConferencingSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isVideoConferencingSupported();
+                }
             }
         }
         return false;
     }
 
     /**
+     * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} allows
+     * merging of wifi calls when VoWIFI is disabled.
+     *
+     * @param handle The {@link PhoneAccountHandle}.
+     * @return {@code True} if merging of wifi calls is allowed when VoWIFI is disabled.
+     */
+    boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
+        synchronized (mAccountsLock) {
+            Optional<AccountEntry> result = mAccounts.stream().filter(
+                    entry -> entry.getPhoneAccountHandle().equals(handle)).findFirst();
+
+            if (result.isPresent()) {
+                return result.get().isMergeOfWifiCallsAllowedWhenVoWifiOff();
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
      * @return Reference to the {@code TelecomAccountRegistry}'s subscription manager.
      */
     SubscriptionManager getSubscriptionManager() {
@@ -486,9 +537,11 @@
      * @return The address.
      */
     Uri getAddress(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.mAccount.getAddress();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.mAccount.getAddress();
+                }
             }
         }
         return null;
@@ -521,9 +574,11 @@
      * @return {@code True} if an entry exists.
      */
     boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return true;
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return true;
+                }
             }
         }
         return false;
@@ -562,28 +617,31 @@
         final boolean phoneAccountsEnabled = mContext.getResources().getBoolean(
                 R.bool.config_pstn_phone_accounts_enabled);
 
-        if (phoneAccountsEnabled) {
-            for (Phone phone : phones) {
-                int subscriptionId = phone.getSubId();
-                Log.d(this, "Phone with subscription id %d", subscriptionId);
-                if (subscriptionId >= 0) {
-                    mAccounts.add(new AccountEntry(phone, false /* emergency */,
-                            false /* isDummy */));
+        synchronized (mAccountsLock) {
+            if (phoneAccountsEnabled) {
+                for (Phone phone : phones) {
+                    int subscriptionId = phone.getSubId();
+                    Log.d(this, "Phone with subscription id %d", subscriptionId);
+                    if (subscriptionId >= 0) {
+                        mAccounts.add(new AccountEntry(phone, false /* emergency */,
+                                false /* isDummy */));
+                    }
                 }
             }
-        }
 
-        // If we did not list ANY accounts, we need to provide a "default" SIM account
-        // for emergency numbers since no actual SIM is needed for dialing emergency
-        // numbers but a phone account is.
-        if (mAccounts.isEmpty()) {
-            mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
-                    false /* isDummy */));
-        }
+            // If we did not list ANY accounts, we need to provide a "default" SIM account
+            // for emergency numbers since no actual SIM is needed for dialing emergency
+            // numbers but a phone account is.
+            if (mAccounts.isEmpty()) {
+                mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
+                        false /* isDummy */));
+            }
 
-        // Add a fake account entry.
-        if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
-            mAccounts.add(new AccountEntry(phones[0], false /* emergency */, true /* isDummy */));
+            // Add a fake account entry.
+            if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
+                mAccounts.add(new AccountEntry(phones[0], false /* emergency */,
+                        true /* isDummy */));
+            }
         }
 
         // Clean up any PhoneAccounts that are no longer relevant
@@ -615,9 +673,11 @@
     }
 
     private void tearDownAccounts() {
-        for (AccountEntry entry : mAccounts) {
-            entry.teardown();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                entry.teardown();
+            }
+            mAccounts.clear();
         }
-        mAccounts.clear();
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index e495de5..07cd7b5 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -27,8 +27,10 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
 import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
 import android.util.Pair;
 
@@ -41,6 +43,8 @@
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 
 import java.lang.Override;
@@ -158,6 +162,10 @@
                 case MSG_SET_VIDEO_STATE:
                     int videoState = (int) msg.obj;
                     setVideoState(videoState);
+
+                    // A change to the video state of the call can influence whether or not it
+                    // can be part of a conference.
+                    refreshConferenceSupported();
                     break;
 
                 case MSG_SET_VIDEO_PROVIDER:
@@ -709,6 +717,10 @@
             if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
                 mTreatAsEmergencyCall = true;
             }
+
+            // Changing the address of the connection can change whether it is an emergency call or
+            // not, which can impact whether it can be part of a conference.
+            refreshConferenceSupported();
         }
     }
 
@@ -759,6 +771,22 @@
         }
         mIsMultiParty = mOriginalConnection.isMultiparty();
 
+        Bundle extrasToPut = new Bundle();
+        List<String> extrasToRemove = new ArrayList<>();
+        if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
+            extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+        } else {
+            extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
+        }
+
+        if (!mOriginalConnection.shouldAllowAddCallDuringVideoCall()) {
+            extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL, true);
+        } else {
+            extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL);
+        }
+        putExtras(extrasToPut);
+        removeExtras(extrasToRemove);
+
         // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
         // should be executed *after* the above setters have run.
         updateState();
@@ -1414,6 +1442,62 @@
     }
 
     /**
+     * Determines whether the connection supports conference calling.  A connection supports
+     * conference calling if it:
+     * 1. Is not an emergency call.
+     * 2. Carrier supports conference calls.
+     * 3. If call is a video call, carrier supports video conference calls.
+     * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls.
+     */
+    private void refreshConferenceSupported() {
+        boolean isVideoCall = VideoProfile.isVideo(getVideoState());
+        Phone phone = getPhone();
+        boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
+        boolean isVoWifiEnabled = false;
+        if (isIms) {
+            ImsPhone imsPhone = (ImsPhone) phone;
+            isVoWifiEnabled = imsPhone.isWifiCallingEnabled();
+        }
+        PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
+                .makePstnPhoneAccountHandle(phone.getDefaultPhone())
+                : PhoneUtils.makePstnPhoneAccountHandle(phone);
+        TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
+                .getInstance(getPhone().getContext());
+        boolean isConferencingSupported = telecomAccountRegistry
+                .isMergeCallSupported(phoneAccountHandle);
+        boolean isVideoConferencingSupported = telecomAccountRegistry
+                .isVideoConferencingSupported(phoneAccountHandle);
+        boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
+                .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
+
+        Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " +
+                "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported,
+                isVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, isWifi(),
+                isVoWifiEnabled);
+        boolean isConferenceSupported = true;
+        if (mTreatAsEmergencyCall) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; emergency call");
+        } else if (!isConferencingSupported) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
+        } else if (isVideoCall && !isVideoConferencingSupported) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
+        } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
+            isConferenceSupported = false;
+            Log.d(this,
+                    "refreshConferenceSupported = false; can't merge wifi calls when voWifi off.");
+        } else {
+            Log.d(this, "refreshConferenceSupported = true.");
+        }
+
+        if (isConferenceSupported != isConferenceSupported()) {
+            setConferenceSupported(isConferenceSupported);
+            notifyConferenceSupportedChanged(isConferenceSupported);
+        }
+    }
+    /**
      * Provides a mapping from extras keys which may be found in the
      * {@link com.android.internal.telephony.Connection} to their equivalents defined in
      * {@link android.telecom.Connection}.
@@ -1466,6 +1550,8 @@
         } else {
             sb.append("Y");
         }
+        sb.append(" confSupported:");
+        sb.append(mIsConferenceSupported ? "Y" : "N");
         sb.append("]");
         return sb.toString();
     }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a4434dd..31d0475 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -175,10 +175,83 @@
             }
         }
 
-        boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number);
+        final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number);
 
-        // Get the right phone object from the account data passed in.
-        final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
+        if (isEmergencyNumber && !isRadioOn()) {
+            final Uri emergencyHandle = handle;
+            // By default, Connection based on the default Phone, since we need to return to Telecom
+            // now.
+            final int defaultPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
+            final Connection emergencyConnection = getTelephonyConnection(request, number,
+                    isEmergencyNumber, emergencyHandle, PhoneFactory.getDefaultPhone());
+            if (mEmergencyCallHelper == null) {
+                mEmergencyCallHelper = new EmergencyCallHelper(this);
+            }
+            mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() {
+                @Override
+                public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
+                    if (isRadioReady) {
+                        // Get the right phone object since the radio has been turned on
+                        // successfully.
+                        final Phone phone = getPhoneForAccount(request.getAccountHandle(),
+                                isEmergencyNumber);
+                        // If the PhoneType of the Phone being used is different than the Default
+                        // Phone, then we need create a new Connection using that PhoneType and
+                        // replace it in Telecom.
+                        if (phone.getPhoneType() != defaultPhoneType) {
+                            Connection repConnection = getTelephonyConnection(request, number,
+                                    isEmergencyNumber, emergencyHandle, phone);
+                            // If there was a failure, the resulting connection will not be a
+                            // TelephonyConnection, so don't place the call, just return!
+                            if (repConnection instanceof TelephonyConnection) {
+                                placeOutgoingConnection((TelephonyConnection) repConnection, phone,
+                                        request);
+                            }
+                            // Notify Telecom of the new Connection type.
+                            // TODO: Switch out the underlying connection instead of creating a new
+                            // one and causing UI Jank.
+                            addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
+                                    repConnection);
+                            // Remove the old connection from Telecom after.
+                            emergencyConnection.setDisconnected(
+                                    DisconnectCauseUtil.toTelecomDisconnectCause(
+                                            android.telephony.DisconnectCause.OUTGOING_CANCELED,
+                                            "Reconnecting outgoing Emergency Call."));
+                            emergencyConnection.destroy();
+                        } else {
+                            placeOutgoingConnection((TelephonyConnection) emergencyConnection,
+                                    phone, request);
+                        }
+                    } else {
+                        Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
+                        emergencyConnection.setDisconnected(
+                                DisconnectCauseUtil.toTelecomDisconnectCause(
+                                        android.telephony.DisconnectCause.POWER_OFF,
+                                        "Failed to turn on radio."));
+                        emergencyConnection.destroy();
+                    }
+                }
+            });
+            // Return the still unconnected GsmConnection and wait for the Radios to boot before
+            // connecting it to the underlying Phone.
+            return emergencyConnection;
+        } else {
+            // Get the right phone object from the account data passed in.
+            final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
+            Connection resultConnection = getTelephonyConnection(request, number, isEmergencyNumber,
+                    handle, phone);
+            // If there was a failure, the resulting connection will not be a TelephonyConnection,
+            // so don't place the call!
+            if(resultConnection instanceof TelephonyConnection) {
+                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+            }
+            return resultConnection;
+        }
+    }
+
+    private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
+            boolean isEmergencyNumber, final Uri handle, Phone phone) {
+
         if (phone == null) {
             final Context context = getApplicationContext();
             if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
@@ -222,11 +295,12 @@
         // when voice RAT is OOS but Data RAT is present.
         int state = phone.getServiceState().getState();
         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
-            if (phone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) {
+            int dataNetType = phone.getServiceState().getDataNetworkType();
+            if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
+                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
                 state = phone.getServiceState().getDataRegState();
             }
         }
-        boolean useEmergencyCallHelper = false;
 
         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
@@ -248,11 +322,7 @@
             }
         }
 
-        if (isEmergencyNumber) {
-            if (!phone.isRadioOn()) {
-                useEmergencyCallHelper = true;
-            }
-        } else {
+        if (!isEmergencyNumber) {
             switch (state) {
                 case ServiceState.STATE_IN_SERVICE:
                 case ServiceState.STATE_EMERGENCY_ONLY:
@@ -307,34 +377,6 @@
         connection.setInitializing();
         connection.setVideoState(request.getVideoState());
 
-        if (useEmergencyCallHelper) {
-            if (mEmergencyCallHelper == null) {
-                mEmergencyCallHelper = new EmergencyCallHelper(this);
-            }
-            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
-                    new EmergencyCallHelper.Callback() {
-                        @Override
-                        public void onComplete(boolean isRadioReady) {
-                            if (connection.getState() == Connection.STATE_DISCONNECTED) {
-                                // If the connection has already been disconnected, do nothing.
-                            } else if (isRadioReady) {
-                                connection.setInitialized();
-                                placeOutgoingConnection(connection, phone, request);
-                            } else {
-                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
-                                connection.setDisconnected(
-                                        DisconnectCauseUtil.toTelecomDisconnectCause(
-                                                android.telephony.DisconnectCause.POWER_OFF,
-                                                "Failed to turn on radio."));
-                                connection.destroy();
-                            }
-                        }
-                    });
-
-        } else {
-            placeOutgoingConnection(connection, phone, request);
-        }
-
         return connection;
     }
 
@@ -512,6 +554,14 @@
 
     }
 
+    private boolean isRadioOn() {
+        boolean result = false;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            result |= phone.isRadioOn();
+        }
+        return result;
+    }
+
     private void placeOutgoingConnection(
             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
         String number = connection.getAddress().getSchemeSpecificPart();
@@ -573,15 +623,6 @@
             returnConnection.setVideoPauseSupported(
                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
                             phoneAccountHandle));
-            boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber(
-                    address.getSchemeSpecificPart()));
-            boolean isVideoCall = VideoProfile.isVideo(videoState);
-            boolean isConferencingSupported = TelecomAccountRegistry.getInstance(this)
-                    .isMergeCallSupported(phoneAccountHandle);
-            boolean isVideoConferencingSupported = TelecomAccountRegistry.getInstance(this)
-                    .isVideoConferencingSupported(phoneAccountHandle);
-            returnConnection.setConferenceSupported(!isEmergencyCall && isConferencingSupported
-                    && (!isVideoCall || (isVideoCall && isVideoConferencingSupported)));
         }
         return returnConnection;
     }
diff --git a/tests/Android.mk b/tests/Android.mk
index 6cc0355..e1b564f 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -25,6 +25,8 @@
 
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_JAVA_LIBRARIES := telephony-common
+
 LOCAL_INSTRUMENTATION_FOR := TeleService
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 8900568..cae4c1b 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -62,17 +62,16 @@
     </application>
 
     <!--
-        The prefered way is to use 'runtest':
-           runtest phone-unit
+        To run all tests:
+            adb shell am instrument -w
+                com.android.phone.tests/android.support.test.runner.AndroidJUnitRunner
 
-         runtest is a wrapper around 'adb shell'. The low level shell command is:
-           adb shell am instrument -w com.android.phone.tests/android.test.InstrumentationTestRunner
+        To run a single class test:
+            adb shell am instrument -e class com.android.phone.unit.FooUnitTest
+                -w com.android.phone.tests/android.support.test.runner.AndroidJUnitRunner
 
-         To run a single test case:
-           adb shell am instrument -w com.android.phone.tests/android.test.InstrumentationTestRunner
-                                   -e com.android.phone.unit.FooUnitTest
     -->
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.phone"
         android:label="Phone application tests." />
 </manifest>
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
new file mode 100644
index 0000000..6dee12b
--- /dev/null
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.content.Context;
+import android.os.Handler;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.phone.MockitoHelper;
+
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to load Mockito Resources into a test.
+ */
+public class TelephonyTestBase {
+
+    protected Context mContext;
+    MockitoHelper mMockitoHelper = new MockitoHelper();
+
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mMockitoHelper.setUp(mContext, getClass());
+        MockitoAnnotations.initMocks(this);
+    }
+
+    public void tearDown() throws Exception {
+        mMockitoHelper.tearDown();
+    }
+
+    protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.post(lock::countDown);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+
+    protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.postDelayed(lock::countDown, delayMs);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/phone/MockitoHelper.java b/tests/src/com/android/phone/MockitoHelper.java
index 3da5d6e..7998030 100644
--- a/tests/src/com/android/phone/MockitoHelper.java
+++ b/tests/src/com/android/phone/MockitoHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.phone;
 
+import android.content.Context;
+
 import com.android.services.telephony.Log;
 
 /**
@@ -24,6 +26,7 @@
 public final class MockitoHelper {
 
     private static final String TAG = "MockitoHelper";
+    private static final String DEXCACHE = "dexmaker.dexcache";
 
     private ClassLoader mOriginalClassLoader;
     private Thread mContextThread;
@@ -34,7 +37,7 @@
      *
      * @param packageClass test case class
      */
-    public void setUp(Class<?> packageClass) throws Exception {
+    public void setUp(Context context, Class<?> packageClass) throws Exception {
         // makes a copy of the context classloader
         mContextThread = Thread.currentThread();
         mOriginalClassLoader = mContextThread.getContextClassLoader();
@@ -42,6 +45,9 @@
         Log.v(TAG, "Changing context classloader from " + mOriginalClassLoader
                 + " to " + newClassLoader);
         mContextThread.setContextClassLoader(newClassLoader);
+        String dexCache = context.getCacheDir().toString();
+        Log.v(this, "Setting property %s to %s", DEXCACHE, dexCache);
+        System.setProperty(DEXCACHE, dexCache);
     }
 
     /**
@@ -50,5 +56,6 @@
     public void tearDown() throws Exception {
         Log.v(TAG, "Restoring context classloader to " + mOriginalClassLoader);
         mContextThread.setContextClassLoader(mOriginalClassLoader);
+        System.clearProperty(DEXCACHE);
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/phone/common/mail/MailTransportTest.java b/tests/src/com/android/phone/common/mail/MailTransportTest.java
index 6acd517..9eaef6b 100644
--- a/tests/src/com/android/phone/common/mail/MailTransportTest.java
+++ b/tests/src/com/android/phone/common/mail/MailTransportTest.java
@@ -61,7 +61,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mMokitoHelper.setUp(getClass());
+        mMokitoHelper.setUp(getContext(), getClass());
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java b/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
new file mode 100644
index 0000000..64cf052
--- /dev/null
+++ b/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 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.services.telephony;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests the EmergencyCallStateListener, which listens to one Phone and waits until its service
+ * state changes to accepting emergency calls or in service. If it can not find a tower to camp onto
+ * for emergency calls, then it will fail after a timeout period.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EmergencyCallStateListenerTest extends TelephonyTestBase {
+
+    private static final long TIMEOUT_MS = 100;
+
+    @Mock Phone mMockPhone;
+    @Mock EmergencyCallStateListener.Callback mCallback;
+    EmergencyCallStateListener mListener;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mListener = new EmergencyCallStateListener();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mListener.getHandler().removeCallbacksAndMessages(null);
+        super.tearDown();
+    }
+
+    @Test
+    public void testRegisterForCallback() {
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
+        verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
+                eq(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
+    }
+
+    @Test
+    public void testPhoneChangeState_InService() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_IN_SERVICE);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testPhoneChangeState_EmergencyCalls() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        state.setEmergencyOnly(true);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testPhoneChangeState_OutOfService() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        // Don't expect any answer, since it is not the one that we want and the timeout for giving
+        // up hasn't expired yet.
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback, never()).onComplete(any(EmergencyCallStateListener.class), anyBoolean());
+    }
+
+    @Test
+    public void testTimeout_EmergencyCalls() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        state.setEmergencyOnly(true);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        mListener.setTimeBetweenRetriesMillis(500);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, 600);
+
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testTimeout_RetryFailure() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        mListener.setTimeBetweenRetriesMillis(100);
+        mListener.setMaxNumRetries(2);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, 600);
+
+        verify(mCallback).onComplete(eq(mListener), eq(false));
+        verify(mMockPhone, times(2)).setRadioPower(eq(true));
+    }
+
+}