Make Telecom synchronous

Remove main-thread handlers and add a systemwide lock object.

Change-Id: I6334fdfa6c244836f6375bea302404f61c81301b
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index e65d633..fe9ddd1 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -25,10 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.telecom.CallState;
 import android.telecom.Connection;
@@ -49,37 +46,9 @@
  * and accepts call-related commands to perform on behalf of the BT device.
  */
 public final class BluetoothPhoneServiceImpl {
-    /**
-     * Request object for performing synchronous requests to the main thread.
-     */
-    private static class MainThreadRequest {
-        private static final Object RESULT_NOT_SET = new Object();
-        Object result = RESULT_NOT_SET;
-        int param;
-
-        MainThreadRequest(int param) {
-            this.param = param;
-        }
-
-        void setResult(Object value) {
-            result = value;
-            synchronized (this) {
-                notifyAll();
-            }
-        }
-    }
 
     private static final String TAG = "BluetoothPhoneService";
 
-    private static final int MSG_ANSWER_CALL = 1;
-    private static final int MSG_HANGUP_CALL = 2;
-    private static final int MSG_SEND_DTMF = 3;
-    private static final int MSG_PROCESS_CHLD = 4;
-    private static final int MSG_GET_NETWORK_OPERATOR = 5;
-    private static final int MSG_LIST_CURRENT_CALLS = 6;
-    private static final int MSG_QUERY_PHONE_STATE = 7;
-    private static final int MSG_GET_SUBSCRIBER_NUMBER = 8;
-
     // match up with bthf_call_state_t of bt_hf.h
     private static final int CALL_STATE_ACTIVE = 0;
     private static final int CALL_STATE_HELD = 1;
@@ -113,206 +82,142 @@
     private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
         @Override
         public boolean answerCall() throws RemoteException {
-            enforceModifyPermission();
-            Log.i(TAG, "BT - answering call");
-            return sendSynchronousRequest(MSG_ANSWER_CALL);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "BT - answering call");
+                Call call = mCallsManager.getRingingCall();
+                if (call != null) {
+                    mCallsManager.answerCall(call, 0);
+                    return true;
+                }
+                return false;
+            }
         }
 
         @Override
         public boolean hangupCall() throws RemoteException {
-            enforceModifyPermission();
-            Log.i(TAG, "BT - hanging up call");
-            return sendSynchronousRequest(MSG_HANGUP_CALL);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "BT - hanging up call");
+                Call call = mCallsManager.getForegroundCall();
+                if (call != null) {
+                    mCallsManager.disconnectCall(call);
+                    return true;
+                }
+                return false;
+            }
         }
 
         @Override
         public boolean sendDtmf(int dtmf) throws RemoteException {
-            enforceModifyPermission();
-            Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
-            return sendSynchronousRequest(MSG_SEND_DTMF, dtmf);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
+                Call call = mCallsManager.getForegroundCall();
+                if (call != null) {
+                    // TODO: Consider making this a queue instead of starting/stopping
+                    // in quick succession.
+                    mCallsManager.playDtmfTone(call, (char) dtmf);
+                    mCallsManager.stopDtmfTone(call);
+                    return true;
+                }
+                return false;
+            }
         }
 
         @Override
         public String getNetworkOperator() throws RemoteException {
-            Log.i(TAG, "getNetworkOperator");
-            enforceModifyPermission();
-            return sendSynchronousRequest(MSG_GET_NETWORK_OPERATOR);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "getNetworkOperator");
+                PhoneAccount account = getBestPhoneAccount();
+                if (account != null) {
+                    return account.getLabel().toString();
+                } else {
+                    // Finally, just get the network name from telephony.
+                    return TelephonyManager.from(mContext)
+                            .getNetworkOperatorName();
+                }
+            }
         }
 
         @Override
         public String getSubscriberNumber() throws RemoteException {
-            Log.i(TAG, "getSubscriberNumber");
-            enforceModifyPermission();
-            return sendSynchronousRequest(MSG_GET_SUBSCRIBER_NUMBER);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "getSubscriberNumber");
+                String address = null;
+                PhoneAccount account = getBestPhoneAccount();
+                if (account != null) {
+                    Uri addressUri = account.getAddress();
+                    if (addressUri != null) {
+                        address = addressUri.getSchemeSpecificPart();
+                    }
+                }
+                if (TextUtils.isEmpty(address)) {
+                    address = TelephonyManager.from(mContext).getLine1Number();
+                }
+                return address;
+            }
         }
 
         @Override
         public boolean listCurrentCalls() throws RemoteException {
-            // only log if it is after we recently updated the headset state or else it can clog
-            // the android log since this can be queried every second.
-            boolean logQuery = mHeadsetUpdatedRecently;
-            mHeadsetUpdatedRecently = false;
+            synchronized (mLock) {
+                enforceModifyPermission();
+                // only log if it is after we recently updated the headset state or else it can clog
+                // the android log since this can be queried every second.
+                boolean logQuery = mHeadsetUpdatedRecently;
+                mHeadsetUpdatedRecently = false;
 
-            if (logQuery) {
-                Log.i(TAG, "listcurrentCalls");
+                if (logQuery) {
+                    Log.i(TAG, "listcurrentCalls");
+                }
+
+                sendListOfCalls(logQuery);
+                return true;
             }
-            enforceModifyPermission();
-            return sendSynchronousRequest(MSG_LIST_CURRENT_CALLS, logQuery ? 1 : 0);
         }
 
         @Override
         public boolean queryPhoneState() throws RemoteException {
-            Log.i(TAG, "queryPhoneState");
-            enforceModifyPermission();
-            return sendSynchronousRequest(MSG_QUERY_PHONE_STATE);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "queryPhoneState");
+                updateHeadsetWithCallState(true /* force */);
+                return true;
+            }
         }
 
         @Override
         public boolean processChld(int chld) throws RemoteException {
-            Log.i(TAG, "processChld %d", chld);
-            enforceModifyPermission();
-            return sendSynchronousRequest(MSG_PROCESS_CHLD, chld);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                Log.i(TAG, "processChld %d", chld);
+                return BluetoothPhoneServiceImpl.this.processChld(chld);
+            }
         }
 
         @Override
         public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
-            Log.d(TAG, "RAT change");
+            Log.d(TAG, "RAT change - deprecated");
             // deprecated
         }
 
         @Override
         public void cdmaSetSecondCallState(boolean state) throws RemoteException {
-            Log.d(TAG, "cdma 1");
+            Log.d(TAG, "cdma 1 - deprecated");
             // deprecated
         }
 
         @Override
         public void cdmaSwapSecondCallState() throws RemoteException {
-            Log.d(TAG, "cdma 2");
+            Log.d(TAG, "cdma 2 - deprecated");
             // deprecated
         }
     };
 
     /**
-     * Main-thread handler for BT commands.  Since telecom logic runs on a single thread, commands
-     * that are sent to it from the headset need to be moved over to the main thread before
-     * executing. This handler exists for that reason.
-     */
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            MainThreadRequest request = msg.obj instanceof MainThreadRequest ?
-                    (MainThreadRequest) msg.obj : null;
-            CallsManager callsManager = mCallsManager;
-            Call call = null;
-
-            Log.d(TAG, "handleMessage(%d) w/ param %s",
-                    msg.what, request == null ? null : request.param);
-
-            switch (msg.what) {
-                case MSG_ANSWER_CALL:
-                    try {
-                        call = callsManager.getRingingCall();
-                        if (call != null) {
-                            mCallsManager.answerCall(call, 0);
-                        }
-                    } finally {
-                        request.setResult(call != null);
-                    }
-                    break;
-
-                case MSG_HANGUP_CALL:
-                    try {
-                        call = callsManager.getForegroundCall();
-                        if (call != null) {
-                            callsManager.disconnectCall(call);
-                        }
-                    } finally {
-                        request.setResult(call != null);
-                    }
-                    break;
-
-                case MSG_SEND_DTMF:
-                    try {
-                        call = callsManager.getForegroundCall();
-                        if (call != null) {
-                            // TODO: Consider making this a queue instead of starting/stopping
-                            // in quick succession.
-                            callsManager.playDtmfTone(call, (char) request.param);
-                            callsManager.stopDtmfTone(call);
-                        }
-                    } finally {
-                        request.setResult(call != null);
-                    }
-                    break;
-
-                case MSG_PROCESS_CHLD:
-                    Boolean result = false;
-                    try {
-                        result = processChld(request.param);
-                    } finally {
-                        request.setResult(result);
-                    }
-                    break;
-
-                case MSG_GET_SUBSCRIBER_NUMBER:
-                    String address = null;
-                    try {
-                        PhoneAccount account = getBestPhoneAccount();
-                        if (account != null) {
-                            Uri addressUri = account.getAddress();
-                            if (addressUri != null) {
-                                address = addressUri.getSchemeSpecificPart();
-                            }
-                        }
-
-                        if (TextUtils.isEmpty(address)) {
-                            address = TelephonyManager.from(mContext)
-                                    .getLine1Number();
-                        }
-                    } finally {
-                        request.setResult(address);
-                    }
-                    break;
-
-                case MSG_GET_NETWORK_OPERATOR:
-                    String label = null;
-                    try {
-                        PhoneAccount account = getBestPhoneAccount();
-                        if (account != null) {
-                            label = account.getLabel().toString();
-                        } else {
-                            // Finally, just get the network name from telephony.
-                            label = TelephonyManager.from(mContext)
-                                    .getNetworkOperatorName();
-                        }
-                    } finally {
-                        request.setResult(label);
-                    }
-                    break;
-
-                case MSG_LIST_CURRENT_CALLS:
-                    try {
-                        sendListOfCalls(request.param == 1);
-                    } finally {
-                        request.setResult(true);
-                    }
-                    break;
-
-                case MSG_QUERY_PHONE_STATE:
-                    try {
-                        updateHeadsetWithCallState(true /* force */);
-                    } finally {
-                        if (request != null) {
-                            request.setResult(true);
-                        }
-                    }
-                    break;
-            }
-        }
-    };
-
-    /**
      * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
      * headset with the new states.
      */
@@ -399,12 +304,16 @@
             new BluetoothProfile.ServiceListener() {
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            synchronized (mLock) {
+                mBluetoothHeadset = (BluetoothHeadset) proxy;
+            }
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
-            mBluetoothHeadset = null;
+            synchronized (mLock) {
+                mBluetoothHeadset = null;
+            }
         }
     };
 
@@ -414,10 +323,17 @@
     private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-            Log.d(TAG, "Bluetooth Adapter state: %d", state);
-            if (state == BluetoothAdapter.STATE_ON) {
-                mHandler.sendEmptyMessage(MSG_QUERY_PHONE_STATE);
+            synchronized (mLock) {
+                int state = intent
+                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+                Log.d(TAG, "Bluetooth Adapter state: %d", state);
+                if (state == BluetoothAdapter.STATE_ON) {
+                    try {
+                        mBinder.queryPhoneState();
+                    } catch (RemoteException e) {
+                        // Remote exception not expected
+                    }
+                }
             }
         }
     };
@@ -430,9 +346,10 @@
 
     private boolean mHeadsetUpdatedRecently = false;
 
-    private Context mContext;
-    private CallsManager mCallsManager;
-    private PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final Context mContext;
+    private final TelecomSystem.SyncRoot mLock;
+    private final CallsManager mCallsManager;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
 
     public IBinder getBinder() {
         return mBinder;
@@ -440,11 +357,13 @@
 
     public BluetoothPhoneServiceImpl(
             Context context,
+            TelecomSystem.SyncRoot lock,
             CallsManager callsManager,
             PhoneAccountRegistrar phoneAccountRegistrar) {
         Log.d(this, "onCreate");
 
         mContext = context;
+        mLock = lock;
         mCallsManager = callsManager;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
 
@@ -463,29 +382,28 @@
     }
 
     private boolean processChld(int chld) {
-        CallsManager callsManager = mCallsManager;
-        Call activeCall = callsManager.getActiveCall();
-        Call ringingCall = callsManager.getRingingCall();
-        Call heldCall = callsManager.getHeldCall();
+        Call activeCall = mCallsManager.getActiveCall();
+        Call ringingCall = mCallsManager.getRingingCall();
+        Call heldCall = mCallsManager.getHeldCall();
 
         // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
         Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
 
         if (chld == CHLD_TYPE_RELEASEHELD) {
             if (ringingCall != null) {
-                callsManager.rejectCall(ringingCall, false, null);
+                mCallsManager.rejectCall(ringingCall, false, null);
                 return true;
             } else if (heldCall != null) {
-                callsManager.disconnectCall(heldCall);
+                mCallsManager.disconnectCall(heldCall);
                 return true;
             }
         } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
             if (activeCall != null) {
-                callsManager.disconnectCall(activeCall);
+                mCallsManager.disconnectCall(activeCall);
                 if (ringingCall != null) {
-                    callsManager.answerCall(ringingCall, 0);
+                    mCallsManager.answerCall(ringingCall, 0);
                 } else if (heldCall != null) {
-                    callsManager.unholdCall(heldCall);
+                    mCallsManager.unholdCall(heldCall);
                 }
                 return true;
             }
@@ -494,15 +412,15 @@
                 activeCall.swapConference();
                 return true;
             } else if (ringingCall != null) {
-                callsManager.answerCall(ringingCall, 0);
+                mCallsManager.answerCall(ringingCall, 0);
                 return true;
             } else if (heldCall != null) {
                 // CallsManager will hold any active calls when unhold() is called on a
                 // currently-held call.
-                callsManager.unholdCall(heldCall);
+                mCallsManager.unholdCall(heldCall);
                 return true;
             } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
-                callsManager.holdCall(activeCall);
+                mCallsManager.holdCall(activeCall);
                 return true;
             }
         } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
@@ -513,7 +431,7 @@
                 } else {
                     List<Call> conferenceable = activeCall.getConferenceableCalls();
                     if (!conferenceable.isEmpty()) {
-                        callsManager.conference(activeCall, conferenceable.get(0));
+                        mCallsManager.conference(activeCall, conferenceable.get(0));
                         return true;
                    }
                 }
@@ -527,35 +445,6 @@
                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
     }
 
-    private <T> T sendSynchronousRequest(int message) {
-        return sendSynchronousRequest(message, 0);
-    }
-
-    private <T> T sendSynchronousRequest(int message, int param) {
-        if (Looper.myLooper() == mHandler.getLooper()) {
-            Log.w(TAG, "This method will deadlock if called from the main thread.");
-        }
-
-        MainThreadRequest request = new MainThreadRequest(param);
-        mHandler.obtainMessage(message, request).sendToTarget();
-        synchronized (request) {
-            while (request.result == MainThreadRequest.RESULT_NOT_SET) {
-                try {
-                    request.wait();
-                } catch (InterruptedException e) {
-                    // Do nothing, go back and wait until the request is complete.
-                    Log.e(TAG, e, "InterruptedException");
-                }
-            }
-        }
-        if (request.result != null) {
-            @SuppressWarnings("unchecked")
-            T retval = (T) request.result;
-            return retval;
-        }
-        return null;
-    }
-
     private void sendListOfCalls(boolean shouldLog) {
         Collection<Call> mCalls = mCallsManager.getCalls();
         for (Call call : mCalls) {
@@ -670,9 +559,9 @@
      */
     private void updateHeadsetWithCallState(boolean force) {
         CallsManager callsManager = mCallsManager;
-        Call activeCall = callsManager.getActiveCall();
-        Call ringingCall = callsManager.getRingingCall();
-        Call heldCall = callsManager.getHeldCall();
+        Call activeCall = mCallsManager.getActiveCall();
+        Call ringingCall = mCallsManager.getRingingCall();
+        Call heldCall = mCallsManager.getHeldCall();
 
         int bluetoothCallState = getBluetoothCallStateForUpdate();
 
@@ -689,7 +578,7 @@
         }
 
         int numActiveCalls = activeCall == null ? 0 : 1;
-        int numHeldCalls = callsManager.getNumHeldCalls();
+        int numHeldCalls = mCallsManager.getNumHeldCalls();
 
         // For conference calls which support swapping the active call within the conference
         // (namely CDMA calls) we need to expose that as a held call in order for the BT device
@@ -783,8 +672,8 @@
 
     private int getBluetoothCallStateForUpdate() {
         CallsManager callsManager = mCallsManager;
-        Call ringingCall = callsManager.getRingingCall();
-        Call dialingCall = callsManager.getDialingCall();
+        Call ringingCall = mCallsManager.getRingingCall();
+        Call dialingCall = mCallsManager.getDialingCall();
 
         //
         // !! WARNING !!
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 45072e4..9e29fe1 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -300,6 +300,7 @@
     private boolean mIsVoipAudioMode;
     private StatusHints mStatusHints;
     private final ConnectionServiceRepository mRepository;
+    private final ContactsAsyncHelper mContactsAsyncHelper;
     private final Context mContext;
     private final CallsManager mCallsManager;
 
@@ -331,6 +332,7 @@
             Context context,
             CallsManager callsManager,
             ConnectionServiceRepository repository,
+            ContactsAsyncHelper contactsAsyncHelper,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -341,6 +343,7 @@
         mContext = context;
         mCallsManager = callsManager;
         mRepository = repository;
+        mContactsAsyncHelper = contactsAsyncHelper;
         setHandle(handle);
         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
         mGatewayInfo = gatewayInfo;
@@ -370,6 +373,7 @@
             Context context,
             CallsManager callsManager,
             ConnectionServiceRepository repository,
+            ContactsAsyncHelper contactsAsyncHelper,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -377,7 +381,7 @@
             boolean isIncoming,
             boolean isConference,
             long connectTimeMillis) {
-        this(context, callsManager, repository, handle, gatewayInfo,
+        this(context, callsManager, repository, contactsAsyncHelper, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
                 isConference);
 
@@ -1277,7 +1281,7 @@
             if (mCallerInfo.contactDisplayPhotoUri != null) {
                 Log.d(this, "Searching person uri %s for call %s",
                         mCallerInfo.contactDisplayPhotoUri, this);
-                ContactsAsyncHelper.startObtainPhotoAsync(
+                mContactsAsyncHelper.startObtainPhotoAsync(
                         token,
                         mContext,
                         mCallerInfo.contactDisplayPhotoUri,
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 3b090a5..7a84eac 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -239,8 +239,6 @@
      * @param isPlayingNew The status to set.
      */
     void setIsTonePlaying(boolean isPlayingNew) {
-        ThreadUtil.checkOnMainThread();
-
         if (mIsTonePlaying != isPlayingNew) {
             Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
             mIsTonePlaying = isPlayingNew;
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index 729db0a..8199dfa 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -79,13 +79,10 @@
     private static int sIdCount;
 
     CallIdMapper(String callIdPrefix) {
-        ThreadUtil.checkOnMainThread();
         mCallIdPrefix = callIdPrefix + "@";
     }
 
     void replaceCall(Call newCall, Call callToReplace) {
-        ThreadUtil.checkOnMainThread();
-
         // Use the old call's ID for the new call.
         String callId = getCallId(callToReplace);
         mCalls.put(callId, newCall);
@@ -95,12 +92,10 @@
         if (call == null) {
             return;
         }
-        ThreadUtil.checkOnMainThread();
         mCalls.put(id, call);
     }
 
     void addCall(Call call) {
-        ThreadUtil.checkOnMainThread();
         addCall(call, getNewId());
     }
 
@@ -108,12 +103,10 @@
         if (call == null) {
             return;
         }
-        ThreadUtil.checkOnMainThread();
         mCalls.removeValue(call);
     }
 
     void removeCall(String callId) {
-        ThreadUtil.checkOnMainThread();
         mCalls.remove(callId);
     }
 
@@ -121,13 +114,10 @@
         if (call == null) {
             return null;
         }
-        ThreadUtil.checkOnMainThread();
         return mCalls.getKey(call);
     }
 
     Call getCall(Object objId) {
-        ThreadUtil.checkOnMainThread();
-
         String callId = null;
         if (objId instanceof String) {
             callId = (String) objId;
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 3c3b98f..1a82498 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -17,16 +17,16 @@
 import android.widget.Toast;
 
 /**
- * Single point of entry for all outgoing and incoming calls. {@link UserCallIntentProcessor} serves
- * as a trampoline that captures call intents for individual users and forwards it to
- * the {@link CallIntentProcessor} which interacts with the rest of Telecom, both of which run only as
- * the primary user.
+ * Single point of entry for all outgoing and incoming calls.
+ * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
+ * captures call intents for individual users and forwards it to the {@link CallIntentProcessor}
+ * which interacts with the rest of Telecom, both of which run only as the primary user.
  */
 public class CallIntentProcessor {
 
-    static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
-    static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
-    static final String KEY_IS_DEFAULT_DIALER = "is_default_dialer";
+    public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
+    public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
+    public static final String KEY_IS_DEFAULT_DIALER = "is_default_dialer";
 
     private final Context mContext;
     private final CallsManager mCallsManager;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index e6443c8..aaa075a 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -120,6 +120,8 @@
     private final PhoneStateBroadcaster mPhoneStateBroadcaster;
     private final CallLogManager mCallLogManager;
     private final Context mContext;
+    private final TelecomSystem.SyncRoot mLock;
+    private final ContactsAsyncHelper mContactsAsyncHelper;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
@@ -140,14 +142,18 @@
     /**
      * Initializes the required Telecom components.
      */
-     CallsManager(
-             Context context,
-             MissedCallNotifier missedCallNotifier,
-             PhoneAccountRegistrar phoneAccountRegistrar,
-             HeadsetMediaButtonFactory headsetMediaButtonFactory,
-             ProximitySensorManagerFactory proximitySensorManagerFactory,
-             InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+    CallsManager(
+            Context context,
+            TelecomSystem.SyncRoot lock,
+            ContactsAsyncHelper contactsAsyncHelper,
+            MissedCallNotifier missedCallNotifier,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            HeadsetMediaButtonFactory headsetMediaButtonFactory,
+            ProximitySensorManagerFactory proximitySensorManagerFactory,
+            InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
         mContext = context;
+        mLock = lock;
+        mContactsAsyncHelper = contactsAsyncHelper;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
@@ -161,10 +167,10 @@
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
         mCallLogManager = new CallLogManager(context);
-        mInCallController = new InCallController(context, this);
+        mInCallController = new InCallController(context, mLock, this);
         mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
         mConnectionServiceRepository =
-                new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, this);
+                new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
 
         mListeners.add(statusBarNotifier);
@@ -179,6 +185,8 @@
         mListeners.add(mDtmfLocalTonePlayer);
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
+
+        mMissedCallNotifier.updateOnStartup(mLock, this, mContactsAsyncHelper);
     }
 
     public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
@@ -278,11 +286,14 @@
 
             mDtmfLocalTonePlayer.playTone(call, nextChar);
 
+            // TODO: Create a LockedRunnable class that does the synchronization automatically.
             mStopTone = new Runnable() {
                 @Override
                 public void run() {
-                    // Set a timeout to stop the tone in case there isn't another tone to follow.
-                    mDtmfLocalTonePlayer.stopTone(call);
+                    synchronized (mLock) {
+                        // Set a timeout to stop the tone in case there isn't another tone to follow.
+                        mDtmfLocalTonePlayer.stopTone(call);
+                    }
                 }
             };
             mHandler.postDelayed(
@@ -339,9 +350,11 @@
         mHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
-                if (mPendingCallsToDisconnect.remove(call)) {
-                    Log.i(this, "Delayed disconnection of call: %s", call);
-                    call.disconnect();
+                synchronized (mLock) {
+                    if (mPendingCallsToDisconnect.remove(call)) {
+                        Log.i(this, "Delayed disconnection of call: %s", call);
+                        call.disconnect();
+                    }
                 }
             }
         }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
@@ -417,6 +430,7 @@
                 mContext,
                 this,
                 mConnectionServiceRepository,
+                mContactsAsyncHelper,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -437,6 +451,7 @@
                 mContext,
                 this,
                 mConnectionServiceRepository,
+                mContactsAsyncHelper,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -474,6 +489,7 @@
                 mContext,
                 this,
                 mConnectionServiceRepository,
+                mContactsAsyncHelper,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -1056,6 +1072,7 @@
                 mContext,
                 this,
                 mConnectionServiceRepository,
+                mContactsAsyncHelper,
                 null /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -1403,6 +1420,7 @@
                 mContext,
                 this,
                 mConnectionServiceRepository,
+                mContactsAsyncHelper,
                 connection.getHandle() /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index d18d051..a587b59 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -33,22 +33,27 @@
             new HashMap<>();
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final Context mContext;
+    private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
 
     private final ServiceBinder.Listener<ConnectionServiceWrapper> mUnbindListener =
             new ServiceBinder.Listener<ConnectionServiceWrapper>() {
                 @Override
                 public void onUnbind(ConnectionServiceWrapper service) {
-                    mServiceCache.remove(service.getComponentName());
+                    synchronized (mLock) {
+                        mServiceCache.remove(service.getComponentName());
+                    }
                 }
             };
 
     ConnectionServiceRepository(
             PhoneAccountRegistrar phoneAccountRegistrar,
             Context context,
+            TelecomSystem.SyncRoot lock,
             CallsManager callsManager) {
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mContext = context;
+        mLock = lock;
         mCallsManager = callsManager;
     }
 
@@ -62,6 +67,7 @@
                     mPhoneAccountRegistrar,
                     mCallsManager,
                     mContext,
+                    mLock,
                     userHandle);
             service.addListener(mUnbindListener);
             mServiceCache.put(cacheKey, service);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 8255303..1aea31d 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -20,9 +20,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telecom.AudioState;
@@ -39,7 +37,6 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 
-import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IConnectionService;
 import com.android.internal.telecom.IConnectionServiceAdapter;
 import com.android.internal.telecom.IVideoProvider;
@@ -61,338 +58,6 @@
  * {@link IConnectionService}.
  */
 final class ConnectionServiceWrapper extends ServiceBinder {
-    private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1;
-    private static final int MSG_SET_ACTIVE = 2;
-    private static final int MSG_SET_RINGING = 3;
-    private static final int MSG_SET_DIALING = 4;
-    private static final int MSG_SET_DISCONNECTED = 5;
-    private static final int MSG_SET_ON_HOLD = 6;
-    private static final int MSG_SET_RINGBACK_REQUESTED = 7;
-    private static final int MSG_SET_CONNECTION_CAPABILITIES = 8;
-    private static final int MSG_SET_IS_CONFERENCED = 9;
-    private static final int MSG_ADD_CONFERENCE_CALL = 10;
-    private static final int MSG_REMOVE_CALL = 11;
-    private static final int MSG_ON_POST_DIAL_WAIT = 12;
-    private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13;
-    private static final int MSG_SET_VIDEO_PROVIDER = 14;
-    private static final int MSG_SET_IS_VOIP_AUDIO_MODE = 15;
-    private static final int MSG_SET_STATUS_HINTS = 16;
-    private static final int MSG_SET_ADDRESS = 17;
-    private static final int MSG_SET_CALLER_DISPLAY_NAME = 18;
-    private static final int MSG_SET_VIDEO_STATE = 19;
-    private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20;
-    private static final int MSG_ADD_EXISTING_CONNECTION = 21;
-    private static final int MSG_ON_POST_DIAL_CHAR = 22;
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            Call call;
-            switch (msg.what) {
-                case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String callId = (String) args.arg1;
-                        ConnectionRequest request = (ConnectionRequest) args.arg2;
-                        ParcelableConnection connection = (ParcelableConnection) args.arg3;
-                        handleCreateConnectionComplete(callId, request, connection);
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_ACTIVE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.markCallAsActive(call);
-                    } else {
-                        //Log.w(this, "setActive, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_SET_RINGING:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.markCallAsRinging(call);
-                    } else {
-                        //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_SET_DIALING:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.markCallAsDialing(call);
-                    } else {
-                        //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_SET_DISCONNECTED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        DisconnectCause disconnectCause = (DisconnectCause) args.arg2;
-                        Log.d(this, "disconnect call %s %s", disconnectCause, call);
-                        if (call != null) {
-                            mCallsManager
-                                    .markCallAsDisconnected(call, disconnectCause);
-                        } else {
-                            //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_ON_HOLD:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.markCallAsOnHold(call);
-                    } else {
-                        //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_SET_RINGBACK_REQUESTED: {
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.setRingbackRequested(msg.arg1 == 1);
-                    } else {
-                        //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
-                    }
-                    break;
-                }
-                case MSG_SET_CONNECTION_CAPABILITIES: {
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.setConnectionCapabilities(msg.arg1);
-                    } else {
-                        //Log.w(ConnectionServiceWrapper.this,
-                        //      "setConnectionCapabilities, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                }
-                case MSG_SET_IS_CONFERENCED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        Call childCall = mCallIdMapper.getCall(args.arg1);
-                        Log.d(this, "SET_IS_CONFERENCE: %s %s", args.arg1, args.arg2);
-                        if (childCall != null) {
-                            String conferenceCallId = (String) args.arg2;
-                            if (conferenceCallId == null) {
-                                Log.d(this, "unsetting parent: %s", args.arg1);
-                                childCall.setParentCall(null);
-                            } else {
-                                Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
-                                childCall.setParentCall(conferenceCall);
-                            }
-                        } else {
-                            //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_ADD_CONFERENCE_CALL: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String id = (String) args.arg1;
-                        if (mCallIdMapper.getCall(id) != null) {
-                            Log.w(this, "Attempting to add a conference call using an existing " +
-                                    "call id %s", id);
-                            break;
-                        }
-                        ParcelableConference parcelableConference =
-                                (ParcelableConference) args.arg2;
-
-                        // Make sure that there's at least one valid call. For remote connections
-                        // we'll get a add conference msg from both the remote connection service
-                        // and from the real connection service.
-                        boolean hasValidCalls = false;
-                        for (String callId : parcelableConference.getConnectionIds()) {
-                            if (mCallIdMapper.getCall(callId) != null) {
-                                hasValidCalls = true;
-                            }
-                        }
-                        // But don't bail out if the connection count is 0, because that is a valid
-                        // IMS conference state.
-                        if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
-                            Log.d(this, "Attempting to add a conference with no valid calls");
-                            break;
-                        }
-
-                        // need to create a new Call
-                        PhoneAccountHandle phAcc = null;
-                        if (parcelableConference != null &&
-                                parcelableConference.getPhoneAccount() != null) {
-                            phAcc = parcelableConference.getPhoneAccount();
-                        }
-                        Call conferenceCall = mCallsManager.createConferenceCall(
-                                phAcc, parcelableConference);
-                        mCallIdMapper.addCall(conferenceCall, id);
-                        conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
-
-                        Log.d(this, "adding children to conference %s phAcc %s",
-                                parcelableConference.getConnectionIds(), phAcc);
-                        for (String callId : parcelableConference.getConnectionIds()) {
-                            Call childCall = mCallIdMapper.getCall(callId);
-                            Log.d(this, "found child: %s", callId);
-                            if (childCall != null) {
-                                childCall.setParentCall(conferenceCall);
-                            }
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_REMOVE_CALL: {
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        if (call.isAlive()) {
-                            mCallsManager.markCallAsDisconnected(
-                                    call, new DisconnectCause(DisconnectCause.REMOTE));
-                        } else {
-                            mCallsManager.markCallAsRemoved(call);
-                        }
-                    }
-                    break;
-                }
-                case MSG_ON_POST_DIAL_WAIT: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null) {
-                            String remaining = (String) args.arg2;
-                            call.onPostDialWait(remaining);
-                        } else {
-                            //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_ON_POST_DIAL_CHAR: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null) {
-                            char nextChar = (char) args.argi1;
-                            call.onPostDialChar(nextChar);
-                        } else {
-                            //Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_QUERY_REMOTE_CALL_SERVICES: {
-                    queryRemoteConnectionServices((RemoteServiceCallback) msg.obj);
-                    break;
-                }
-                case MSG_SET_VIDEO_PROVIDER: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        IVideoProvider videoProvider = (IVideoProvider) args.arg2;
-                        if (call != null) {
-                            call.setVideoProvider(videoProvider);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_IS_VOIP_AUDIO_MODE: {
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.setIsVoipAudioMode(msg.arg1 == 1);
-                    }
-                    break;
-                }
-                case MSG_SET_STATUS_HINTS: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        StatusHints statusHints = (StatusHints) args.arg2;
-                        if (call != null) {
-                            call.setStatusHints(statusHints);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_ADDRESS: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null) {
-                            call.setHandle((Uri) args.arg2, args.argi1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_CALLER_DISPLAY_NAME: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null) {
-                            call.setCallerDisplayName((String) args.arg2, args.argi1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SET_VIDEO_STATE: {
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.setVideoState(msg.arg1);
-                    }
-                    break;
-                }
-                case MSG_SET_CONFERENCEABLE_CONNECTIONS: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null ){
-                            @SuppressWarnings("unchecked")
-                            List<String> conferenceableIds = (List<String>) args.arg2;
-                            List<Call> conferenceableCalls =
-                                    new ArrayList<>(conferenceableIds.size());
-                            for (String otherId : (List<String>) args.arg2) {
-                                Call otherCall = mCallIdMapper.getCall(otherId);
-                                if (otherCall != null && otherCall != call) {
-                                    conferenceableCalls.add(otherCall);
-                                }
-                            }
-                            call.setConferenceableCalls(conferenceableCalls);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_ADD_EXISTING_CONNECTION: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String callId = (String)args.arg1;
-                        ParcelableConnection connection = (ParcelableConnection)args.arg2;
-                        Call existingCall = mCallsManager
-                                .createCallForExistingConnection(callId,
-                                connection);
-                        mCallIdMapper.addCall(existingCall, callId);
-                        existingCall.setConnectionService(ConnectionServiceWrapper.this);
-                    } finally {
-                        args.recycle();
-                    }
-                }
-            }
-        }
-    };
 
     private final class Adapter extends IConnectionServiceAdapter.Stub {
 
@@ -401,221 +66,361 @@
                 String callId,
                 ConnectionRequest request,
                 ParcelableConnection connection) {
-            logIncoming("handleCreateConnectionComplete %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = request;
-                args.arg3 = connection;
-                mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args)
-                        .sendToTarget();
+            synchronized (mLock) {
+                logIncoming("handleCreateConnectionComplete %s", callId);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    ConnectionServiceWrapper.this
+                            .handleCreateConnectionComplete(callId, request, connection);
+                }
             }
         }
 
         @Override
         public void setActive(String callId) {
-            logIncoming("setActive %s", callId);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setActive %s", callId);
+                if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+                        .isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.markCallAsActive(call);
+                    } else {
+                        //Log.w(this, "setActive, unknown call id: %s", msg.obj);
+                    }
+                }
             }
         }
 
         @Override
         public void setRinging(String callId) {
-            logIncoming("setRinging %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setRinging %s", callId);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.markCallAsRinging(call);
+                    } else {
+                        //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
+                    }
+                }
             }
         }
 
         @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider) {
-            logIncoming("setVideoProvider %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = videoProvider;
-                mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setVideoProvider %s", callId);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setVideoProvider(videoProvider);
+                    }
+                }
             }
         }
 
         @Override
         public void setDialing(String callId) {
-            logIncoming("setDialing %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setDialing %s", callId);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.markCallAsDialing(call);
+                    } else {
+                        //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
+                    }
+                }
             }
         }
 
         @Override
         public void setDisconnected(String callId, DisconnectCause disconnectCause) {
-            logIncoming("setDisconnected %s %s", callId, disconnectCause);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                Log.d(this, "disconnect call %s", callId);
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = disconnectCause;
-                mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setDisconnected %s %s", callId, disconnectCause);
+                if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+                        .isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    Log.d(this, "disconnect call %s %s", disconnectCause, call);
+                    if (call != null) {
+                        mCallsManager.markCallAsDisconnected(call, disconnectCause);
+                    } else {
+                        //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
+                    }
+                }
             }
         }
 
         @Override
         public void setOnHold(String callId) {
-            logIncoming("setOnHold %s", callId);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setOnHold %s", callId);
+                if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+                        .isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.markCallAsOnHold(call);
+                    } else {
+                        //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
+                    }
+                }
             }
         }
 
         @Override
         public void setRingbackRequested(String callId, boolean ringback) {
-            logIncoming("setRingbackRequested %s %b", callId, ringback);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_SET_RINGBACK_REQUESTED, ringback ? 1 : 0, 0, callId)
-                        .sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setRingbackRequested %s %b", callId, ringback);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setRingbackRequested(ringback);
+                    } else {
+                        //Log.w(this, "setRingback, unknown call id: %s", args.arg1);
+                    }
+                }
             }
         }
 
         @Override
         public void removeCall(String callId) {
-            logIncoming("removeCall %s", callId);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("removeCall %s", callId);
+                if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+                        .isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        if (call.isAlive()) {
+                            mCallsManager.markCallAsDisconnected(
+                                    call, new DisconnectCause(DisconnectCause.REMOTE));
+                        } else {
+                            mCallsManager.markCallAsRemoved(call);
+                        }
+                    }
+                }
             }
         }
 
         @Override
         public void setConnectionCapabilities(String callId, int connectionCapabilities) {
-            logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                mHandler.obtainMessage(
-                        MSG_SET_CONNECTION_CAPABILITIES, connectionCapabilities, 0, callId)
-                        .sendToTarget();
-            } else {
-                Log.w(this, "ID not valid for setCallCapabilities");
+            synchronized (mLock) {
+                logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
+                if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
+                        .isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setConnectionCapabilities(connectionCapabilities);
+                    } else {
+                        //Log.w(ConnectionServiceWrapper.this,
+                        //      "setConnectionCapabilities, unknown call id: %s", msg.obj);
+                    }
+                }
             }
         }
 
         @Override
         public void setIsConferenced(String callId, String conferenceCallId) {
-            logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = conferenceCallId;
-            mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
+                Call childCall = mCallIdMapper.getCall(callId);
+                if (childCall != null) {
+                    if (conferenceCallId == null) {
+                        Log.d(this, "unsetting parent: %s", conferenceCallId);
+                        childCall.setParentCall(null);
+                    } else {
+                        Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
+                        childCall.setParentCall(conferenceCall);
+                    }
+                } else {
+                    //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
+                }
+            }
         }
 
         @Override
         public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
-            logIncoming("addConferenceCall %s %s", callId, parcelableConference);
-            // We do not check call Ids here because we do not yet know the call ID for new
-            // conference calls.
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = parcelableConference;
-            mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget();
+            synchronized (mLock) {
+                if (mCallIdMapper.getCall(callId) != null) {
+                    Log.w(this, "Attempting to add a conference call using an existing " +
+                            "call id %s", callId);
+                    return;
+                }
+
+                // Make sure that there's at least one valid call. For remote connections
+                // we'll get a add conference msg from both the remote connection service
+                // and from the real connection service.
+                boolean hasValidCalls = false;
+                for (String connId : parcelableConference.getConnectionIds()) {
+                    if (mCallIdMapper.getCall(connId) != null) {
+                        hasValidCalls = true;
+                    }
+                }
+                // But don't bail out if the connection count is 0, because that is a valid
+                // IMS conference state.
+                if (!hasValidCalls && parcelableConference.getConnectionIds().size() > 0) {
+                    Log.d(this, "Attempting to add a conference with no valid calls");
+                    return;
+                }
+
+                // need to create a new Call
+                PhoneAccountHandle phAcc = null;
+                if (parcelableConference != null &&
+                        parcelableConference.getPhoneAccount() != null) {
+                    phAcc = parcelableConference.getPhoneAccount();
+                }
+                Call conferenceCall = mCallsManager.createConferenceCall(
+                        phAcc, parcelableConference);
+                mCallIdMapper.addCall(conferenceCall, callId);
+                conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+
+                Log.d(this, "adding children to conference %s phAcc %s",
+                        parcelableConference.getConnectionIds(), phAcc);
+                for (String connId : parcelableConference.getConnectionIds()) {
+                    Call childCall = mCallIdMapper.getCall(connId);
+                    Log.d(this, "found child: %s", connId);
+                    if (childCall != null) {
+                        childCall.setParentCall(conferenceCall);
+                    }
+                }
+            }
         }
 
         @Override
         public void onPostDialWait(String callId, String remaining) throws RemoteException {
-            logIncoming("onPostDialWait %s %s", callId, remaining);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = remaining;
-                mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("onPostDialWait %s %s", callId, remaining);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.onPostDialWait(remaining);
+                    } else {
+                        //Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
+                    }
+                }
             }
         }
 
         @Override
         public void onPostDialChar(String callId, char nextChar) throws RemoteException {
-            logIncoming("onPostDialChar %s %s", callId, nextChar);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.argi1 = nextChar;
-                mHandler.obtainMessage(MSG_ON_POST_DIAL_CHAR, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("onPostDialChar %s %s", callId, nextChar);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.onPostDialChar(nextChar);
+                    } else {
+                        //Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
+                    }
+                }
             }
         }
 
         @Override
         public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
-            logIncoming("queryRemoteCSs");
-            mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("queryRemoteConnectionServices %s", callback);
+                ConnectionServiceWrapper.this.queryRemoteConnectionServices(callback);
+            }
         }
 
         @Override
         public void setVideoState(String callId, int videoState) {
-            logIncoming("setVideoState %s %d", callId, videoState);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState, 0, callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setVideoState %s %d", callId, videoState);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setVideoState(videoState);
+                    }
+                }
             }
         }
 
         @Override
         public void setIsVoipAudioMode(String callId, boolean isVoip) {
-            logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_SET_IS_VOIP_AUDIO_MODE, isVoip ? 1 : 0, 0,
-                        callId).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setIsVoipAudioMode(isVoip);
+                    }
+                }
             }
         }
 
         @Override
         public void setStatusHints(String callId, StatusHints statusHints) {
-            logIncoming("setStatusHints %s %s", callId, statusHints);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = statusHints;
-                mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setStatusHints %s %s", callId, statusHints);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setStatusHints(statusHints);
+                    }
+                }
             }
         }
 
         @Override
         public void setAddress(String callId, Uri address, int presentation) {
-            logIncoming("setAddress %s %s %d", callId, address, presentation);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = address;
-                args.argi1 = presentation;
-                mHandler.obtainMessage(MSG_SET_ADDRESS, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setAddress %s %s %d", callId, address, presentation);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setHandle(address, presentation);
+                    }
+                }
             }
         }
 
         @Override
         public void setCallerDisplayName(
                 String callId, String callerDisplayName, int presentation) {
-            logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, presentation);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = callerDisplayName;
-                args.argi1 = presentation;
-                mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName, presentation);
+                if (mCallIdMapper.isValidCallId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setCallerDisplayName(callerDisplayName, presentation);
+                    }
+                }
             }
         }
 
         @Override
         public void setConferenceableConnections(
                 String callId, List<String> conferenceableCallIds) {
-            logIncoming("setConferenceableConnections %s %s", callId, conferenceableCallIds);
-            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = conferenceableCallIds;
-                mHandler.obtainMessage(MSG_SET_CONFERENCEABLE_CONNECTIONS, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("setConferenceableConnections %s %s", callId, conferenceableCallIds);
+                if (mCallIdMapper.isValidCallId(callId) ||
+                        mCallIdMapper.isValidConferenceId(callId)) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null ){
+                        List<Call> conferenceableCalls =
+                                new ArrayList<>(conferenceableCallIds.size());
+                        for (String otherId : conferenceableCallIds) {
+                            Call otherCall = mCallIdMapper.getCall(otherId);
+                            if (otherCall != null && otherCall != call) {
+                                conferenceableCalls.add(otherCall);
+                            }
+                        }
+                        call.setConferenceableCalls(conferenceableCalls);
+                    }
+                }
             }
         }
 
         @Override
         public void addExistingConnection(String callId, ParcelableConnection connection) {
-            logIncoming("addExistingConnection  %s %s", callId, connection);
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = connection;
-            mHandler.obtainMessage(MSG_ADD_EXISTING_CONNECTION, args).sendToTarget();
+            synchronized (mLock) {
+                logIncoming("addExistingConnection  %s %s", callId, connection);
+                Call existingCall = mCallsManager
+                        .createCallForExistingConnection(callId, connection);
+                mCallIdMapper.addCall(existingCall, callId);
+                existingCall.setConnectionService(ConnectionServiceWrapper.this);
+            }
         }
     }
 
@@ -645,8 +450,9 @@
             PhoneAccountRegistrar phoneAccountRegistrar,
             CallsManager callsManager,
             Context context,
+            TelecomSystem.SyncRoot lock,
             UserHandle userHandle) {
-        super(ConnectionService.SERVICE_INTERFACE, componentName, context, userHandle);
+        super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
         mConnectionServiceRepository = connectionServiceRepository;
         phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
             // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 69010d2..ef7ea4c 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -60,36 +60,14 @@
     // constants
     private static final int EVENT_LOAD_IMAGE = 1;
 
-    private static final Handler sResultHandler = new Handler(Looper.getMainLooper()) {
-        /** Called when loading is done. */
-        @Override
-        public void handleMessage(Message msg) {
-            WorkerArgs args = (WorkerArgs) msg.obj;
-            switch (msg.arg1) {
-                case EVENT_LOAD_IMAGE:
-                    if (args.listener != null) {
-                        Log.d(this, "Notifying listener: " + args.listener.toString() +
-                                " image: " + args.displayPhotoUri + " completed");
-                        args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
-                                args.cookie);
-                    }
-                    break;
-                default:
-            }
-        }
-    };
-
     /** Handler run on a worker thread to load photo asynchronously. */
-    private static final Handler sThreadHandler;
+    private Handler mThreadHandler;
+    private final TelecomSystem.SyncRoot mLock;
 
-    static {
-        HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
-        thread.start();
-        sThreadHandler = new WorkerHandler(thread.getLooper());
+    public ContactsAsyncHelper(TelecomSystem.SyncRoot lock) {
+        mLock = lock;
     }
 
-    private ContactsAsyncHelper() {}
-
     private static final class WorkerArgs {
         public Context context;
         public Uri displayPhotoUri;
@@ -103,7 +81,7 @@
      * Thread worker class that handles the task of opening the stream and loading
      * the images.
      */
-    private static class WorkerHandler extends Handler {
+    private class WorkerHandler extends Handler {
         public WorkerHandler(Looper looper) {
             super(looper);
         }
@@ -149,15 +127,16 @@
                             }
                         }
                     }
+                    synchronized (mLock) {
+                        Log.d(this, "Notifying listener: " + args.listener.toString() +
+                                " image: " + args.displayPhotoUri + " completed");
+                        args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
+                                args.cookie);
+                    }
                     break;
                 default:
+                    break;
             }
-
-            // send the reply to the enclosing class.
-            Message reply = sResultHandler.obtainMessage(msg.what);
-            reply.arg1 = msg.arg1;
-            reply.obj = msg.obj;
-            reply.sendToTarget();
         }
 
         /**
@@ -212,9 +191,9 @@
      * fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable,
      * Bitmap, Object)}. Can be null, at which the callback will also has null for the argument.
      */
-    public static final void startObtainPhotoAsync(int token, Context context, Uri displayPhotoUri,
+    public final void startObtainPhotoAsync(int token, Context context, Uri displayPhotoUri,
             OnImageLoadCompleteListener listener, Object cookie) {
-        ThreadUtil.checkOnMainThread();
+        ensureAsyncHandlerStarted();
 
         // in case the source caller info is null, the URI will be null as well.
         // just update using the placeholder image in this case.
@@ -234,7 +213,7 @@
         args.listener = listener;
 
         // setup message arguments
-        Message msg = sThreadHandler.obtainMessage(token);
+        Message msg = mThreadHandler.obtainMessage(token);
         msg.arg1 = EVENT_LOAD_IMAGE;
         msg.obj = args;
 
@@ -242,6 +221,14 @@
                 ", displaying default image for now.");
 
         // notify the thread to begin working
-        sThreadHandler.sendMessage(msg);
+        mThreadHandler.sendMessage(msg);
+    }
+
+    private void ensureAsyncHandlerStarted() {
+        if (mThreadHandler == null) {
+            HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
+            thread.start();
+            mThreadHandler = new WorkerHandler(thread.getLooper());
+        }
     }
 }
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index e39b5a5..d84c87e 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -29,320 +29,234 @@
  * binding to it. This adapter can receive commands and updates until the in-call app is unbound.
  */
 class InCallAdapter extends IInCallAdapter.Stub {
-    private static final int MSG_ANSWER_CALL = 0;
-    private static final int MSG_REJECT_CALL = 1;
-    private static final int MSG_PLAY_DTMF_TONE = 2;
-    private static final int MSG_STOP_DTMF_TONE = 3;
-    private static final int MSG_POST_DIAL_CONTINUE = 4;
-    private static final int MSG_DISCONNECT_CALL = 5;
-    private static final int MSG_HOLD_CALL = 6;
-    private static final int MSG_UNHOLD_CALL = 7;
-    private static final int MSG_MUTE = 8;
-    private static final int MSG_SET_AUDIO_ROUTE = 9;
-    private static final int MSG_CONFERENCE = 10;
-    private static final int MSG_SPLIT_FROM_CONFERENCE = 11;
-    private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 12;
-    private static final int MSG_PHONE_ACCOUNT_SELECTED = 13;
-    private static final int MSG_TURN_ON_PROXIMITY_SENSOR = 14;
-    private static final int MSG_TURN_OFF_PROXIMITY_SENSOR = 15;
-    private static final int MSG_MERGE_CONFERENCE = 16;
-    private static final int MSG_SWAP_CONFERENCE = 17;
-
-    private final class InCallAdapterHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            Call call;
-            switch (msg.what) {
-                case MSG_ANSWER_CALL: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        int videoState = (int) args.arg2;
-                        if (call != null) {
-                            mCallsManager.answerCall(call, videoState);
-                        } else {
-                            Log.w(this, "answerCall, unknown call id: %s", msg.obj);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_REJECT_CALL: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        boolean rejectWithMessage = args.argi1 == 1;
-                        String textMessage = (String) args.arg2;
-                        if (call != null) {
-                            mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
-                        } else {
-                            Log.w(this, "setRingback, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_PLAY_DTMF_TONE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.playDtmfTone(call, (char) msg.arg1);
-                    } else {
-                        Log.w(this, "playDtmfTone, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_STOP_DTMF_TONE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.stopDtmfTone(call);
-                    } else {
-                        Log.w(this, "stopDtmfTone, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_POST_DIAL_CONTINUE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.postDialContinue(call, msg.arg1 == 1);
-                    } else {
-                        Log.w(this, "postDialContinue, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_DISCONNECT_CALL:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.disconnectCall(call);
-                    } else {
-                        Log.w(this, "disconnectCall, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_HOLD_CALL:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.holdCall(call);
-                    } else {
-                        Log.w(this, "holdCall, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_UNHOLD_CALL:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        mCallsManager.unholdCall(call);
-                    } else {
-                        Log.w(this, "unholdCall, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_PHONE_ACCOUNT_SELECTED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        if (call != null) {
-                            mCallsManager.phoneAccountSelected(call,
-                                    (PhoneAccountHandle) args.arg2, args.argi1 == 1);
-                        } else {
-                            Log.w(this, "phoneAccountSelected, unknown call id: %s", args.arg1);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_MUTE:
-                    mCallsManager.mute(msg.arg1 == 1);
-                    break;
-                case MSG_SET_AUDIO_ROUTE:
-                    mCallsManager.setAudioRoute(msg.arg1);
-                    break;
-                case MSG_CONFERENCE: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        call = mCallIdMapper.getCall(args.arg1);
-                        Call otherCall = mCallIdMapper.getCall(args.arg2);
-                        if (call != null && otherCall != null) {
-                            mCallsManager.conference(call, otherCall);
-                        } else {
-                            Log.w(this, "conference, unknown call id: %s", msg.obj);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_SPLIT_FROM_CONFERENCE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.splitFromConference();
-                    } else {
-                        Log.w(this, "splitFromConference, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_TURN_ON_PROXIMITY_SENSOR:
-                    mCallsManager.turnOnProximitySensor();
-                    break;
-                case MSG_TURN_OFF_PROXIMITY_SENSOR:
-                    mCallsManager.turnOffProximitySensor((boolean) msg.obj);
-                    break;
-                case MSG_MERGE_CONFERENCE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.mergeConference();
-                    } else {
-                        Log.w(this, "mergeConference, unknown call id: %s", msg.obj);
-                    }
-                    break;
-                case MSG_SWAP_CONFERENCE:
-                    call = mCallIdMapper.getCall(msg.obj);
-                    if (call != null) {
-                        call.swapConference();
-                    } else {
-                        Log.w(this, "swapConference, unknown call id: %s", msg.obj);
-                    }
-                    break;
-            }
-        }
-    }
-
     private final CallsManager mCallsManager;
-    private final Handler mHandler = new InCallAdapterHandler();
     private final CallIdMapper mCallIdMapper;
+    private final TelecomSystem.SyncRoot mLock;
 
     /** Persists the specified parameters. */
-    public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper) {
-        ThreadUtil.checkOnMainThread();
+    public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper, TelecomSystem.SyncRoot lock) {
         mCallsManager = callsManager;
         mCallIdMapper = callIdMapper;
+        mLock = lock;
     }
 
     @Override
     public void answerCall(String callId, int videoState) {
-        Log.d(this, "answerCall(%s,%d)", callId, videoState);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = videoState;
-            mHandler.obtainMessage(MSG_ANSWER_CALL, args).sendToTarget();
+        synchronized (mLock) {
+            Log.d(this, "answerCall(%s,%d)", callId, videoState);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.answerCall(call, videoState);
+                } else {
+                    Log.w(this, "answerCall, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
-        Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.argi1 = rejectWithMessage ? 1 : 0;
-            args.arg2 = textMessage;
-            mHandler.obtainMessage(MSG_REJECT_CALL, args).sendToTarget();
+        synchronized (this) {
+            Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
+                } else {
+                    Log.w(this, "setRingback, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void playDtmfTone(String callId, char digit) {
-        Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, (int) digit, 0, callId).sendToTarget();
+        synchronized (mLock) {
+            Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.playDtmfTone(call, digit);
+                } else {
+                    Log.w(this, "playDtmfTone, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void stopDtmfTone(String callId) {
-        Log.d(this, "stopDtmfTone(%s)", callId);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
+        synchronized (mLock) {
+            Log.d(this, "stopDtmfTone(%s)", callId);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.stopDtmfTone(call);
+                } else {
+                    Log.w(this, "stopDtmfTone, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void postDialContinue(String callId, boolean proceed) {
-        Log.d(this, "postDialContinue(%s)", callId);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_POST_DIAL_CONTINUE, proceed ? 1 : 0, 0, callId).sendToTarget();
+        synchronized (mLock) {
+            Log.d(this, "postDialContinue(%s)", callId);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.postDialContinue(call, proceed);
+                } else {
+                    Log.w(this, "postDialContinue, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void disconnectCall(String callId) {
-        Log.v(this, "disconnectCall: %s", callId);
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_DISCONNECT_CALL, callId).sendToTarget();
+        synchronized (mLock) {
+            Log.v(this, "disconnectCall: %s", callId);
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.disconnectCall(call);
+                } else {
+                    Log.w(this, "disconnectCall, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void holdCall(String callId) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_HOLD_CALL, callId).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.holdCall(call);
+                } else {
+                    Log.w(this, "holdCall, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void unholdCall(String callId) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_UNHOLD_CALL, callId).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.unholdCall(call);
+                } else {
+                    Log.w(this, "unholdCall, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle,
             boolean setDefault) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = accountHandle;
-            args.argi1 = setDefault? 1 : 0;
-            mHandler.obtainMessage(MSG_PHONE_ACCOUNT_SELECTED, args).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    mCallsManager.phoneAccountSelected(call, accountHandle, setDefault);
+                } else {
+                    Log.w(this, "phoneAccountSelected, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void mute(boolean shouldMute) {
-        mHandler.obtainMessage(MSG_MUTE, shouldMute ? 1 : 0, 0).sendToTarget();
+        synchronized (mLock) {
+            mCallsManager.mute(shouldMute);
+        }
     }
 
     @Override
     public void setAudioRoute(int route) {
-        mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, route, 0).sendToTarget();
+        synchronized (mLock) {
+            mCallsManager.setAudioRoute(route);
+        }
     }
 
     @Override
     public void conference(String callId, String otherCallId) {
-        if (mCallIdMapper.isValidCallId(callId) &&
-                mCallIdMapper.isValidCallId(otherCallId)) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = otherCallId;
-            mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId) &&
+                    mCallIdMapper.isValidCallId(otherCallId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                Call otherCall = mCallIdMapper.getCall(otherCallId);
+                if (call != null && otherCall != null) {
+                    mCallsManager.conference(call, otherCall);
+                } else {
+                    Log.w(this, "conference, unknown call id: %s or %s", callId, otherCallId);
+                }
+
+            }
         }
     }
 
     @Override
     public void splitFromConference(String callId) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    call.splitFromConference();
+                } else {
+                    Log.w(this, "splitFromConference, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void mergeConference(String callId) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    call.mergeConference();
+                } else {
+                    Log.w(this, "mergeConference, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void swapConference(String callId) {
-        if (mCallIdMapper.isValidCallId(callId)) {
-            mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
+        synchronized (mLock) {
+            if (mCallIdMapper.isValidCallId(callId)) {
+                Call call = mCallIdMapper.getCall(callId);
+                if (call != null) {
+                    call.swapConference();
+                } else {
+                    Log.w(this, "swapConference, unknown call id: %s", callId);
+                }
+            }
         }
     }
 
     @Override
     public void turnOnProximitySensor() {
-        mHandler.obtainMessage(MSG_TURN_ON_PROXIMITY_SENSOR).sendToTarget();
+        synchronized (mLock) {
+            mCallsManager.turnOnProximitySensor();
+        }
     }
 
     @Override
     public void turnOffProximitySensor(boolean screenOnImmediately) {
-        mHandler.obtainMessage(MSG_TURN_OFF_PROXIMITY_SENSOR, screenOnImmediately).sendToTarget();
+        synchronized (mLock) {
+            mCallsManager.turnOffProximitySensor(screenOnImmediately);
+        }
     }
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9c2efd9..e959bd7 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -139,10 +139,12 @@
     private final ComponentName mInCallComponentName;
 
     private final Context mContext;
+    private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
 
-    public InCallController(Context context, CallsManager callsManager) {
+    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
         mContext = context;
+        mLock = lock;
         mCallsManager = callsManager;
         Resources resources = mContext.getResources();
 
@@ -260,7 +262,6 @@
      * Unbinds an existing bound connection to the in-call app.
      */
     private void unbind() {
-        ThreadUtil.checkOnMainThread();
         Iterator<Map.Entry<ComponentName, InCallServiceConnection>> iterator =
             mServiceConnections.entrySet().iterator();
         while (iterator.hasNext()) {
@@ -278,7 +279,6 @@
      * @param call The newly added call that triggered the binding to the in-call services.
      */
     private void bind(Call call) {
-        ThreadUtil.checkOnMainThread();
         if (mInCallServices.isEmpty()) {
             PackageManager packageManager = mContext.getPackageManager();
             Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -350,7 +350,6 @@
      * @param service The {@link IInCallService} implementation.
      */
     private void onConnected(ComponentName componentName, IBinder service) {
-        ThreadUtil.checkOnMainThread();
         Trace.beginSection("onConnected: " + componentName);
         Log.i(this, "onConnected to %s", componentName);
 
@@ -360,7 +359,8 @@
             inCallService.setInCallAdapter(
                     new InCallAdapter(
                             mCallsManager,
-                            mCallIdMapper));
+                            mCallIdMapper,
+                            mLock));
             mInCallServices.put(componentName, inCallService);
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
@@ -400,7 +400,6 @@
      */
     private void onDisconnected(ComponentName disconnectedComponent) {
         Log.i(this, "onDisconnected from %s", disconnectedComponent);
-        ThreadUtil.checkOnMainThread();
 
         if (mInCallServices.containsKey(disconnectedComponent)) {
             mInCallServices.remove(disconnectedComponent);
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 5afc67f..3b4380e 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -222,8 +222,6 @@
     }
 
     void startTone() {
-        ThreadUtil.checkOnMainThread();
-
         sTonesPlaying++;
         if (sTonesPlaying == 1) {
             mCallAudioManager.setIsTonePlaying(true);
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 1dec123..52055cf 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -21,9 +21,12 @@
  */
 public interface MissedCallNotifier extends CallsManager.CallsManagerListener {
 
-    void setCallsManager(CallsManager callsManager);
-
     void clearMissedCalls();
 
     void showMissedCallNotification(Call call);
+
+    void updateOnStartup(
+            TelecomSystem.SyncRoot lock,
+            CallsManager callsManager,
+            ContactsAsyncHelper contactsAsyncHelper);
 }
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index eb43991..a2441ce 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import com.android.server.telecom.components.UserCallIntentProcessor;
+
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -156,8 +158,8 @@
      * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
      * - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
      *
-     * @return {@link UserCallIntentProcessor#OUTGOING_CALL_SUCCEEDED} if the call succeeded, and an
-     *         appropriate {@link DisconnectCause} if the call did not, describing why it failed.
+     * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
+     *         {@link DisconnectCause} if the call did not, describing why it failed.
      */
     int processIntent() {
         Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 874ca4c..98ba3b4 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -41,32 +41,15 @@
  * Helper class to manage the "Respond via Message" feature for incoming calls.
  */
 public class RespondViaSmsManager extends CallsManagerListenerBase {
-    private static final int MSG_CANNED_TEXT_MESSAGES_READY = 1;
     private static final int MSG_SHOW_SENT_TOAST = 2;
 
     private final CallsManager mCallsManager;
+    private final TelecomSystem.SyncRoot mLock;
 
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_CANNED_TEXT_MESSAGES_READY: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        Response<Void, List<String>> response =
-                                (Response<Void, List<String>>) args.arg1;
-                        List<String> textMessages =
-                                (List<String>) args.arg2;
-                        if (textMessages != null) {
-                            response.onResult(null, textMessages);
-                        } else {
-                            response.onError(null, 0, null);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
                 case MSG_SHOW_SENT_TOAST: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -82,8 +65,9 @@
         }
     };
 
-    public RespondViaSmsManager(CallsManager callsManager) {
+    public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
         mCallsManager = callsManager;
+        mLock = lock;
     }
 
     /**
@@ -132,10 +116,9 @@
                         "loadCannedResponses() completed, found responses: %s",
                         textMessages.toString());
 
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = response;
-                args.arg2 = textMessages;
-                mHandler.obtainMessage(MSG_CANNED_TEXT_MESSAGES_READY, args).sendToTarget();
+                synchronized (mLock) {
+                    response.onResult(null, textMessages);
+                }
             }
         }.start();
     }
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index e6d703c..6c27388 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -89,7 +89,6 @@
      */
     private void startRingbackForCall(Call call) {
         Preconditions.checkState(call.getState() == CallState.DIALING);
-        ThreadUtil.checkOnMainThread();
 
         if (mCall == call) {
             Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
@@ -116,8 +115,6 @@
      * @param call The call for which to stop ringback.
      */
     private void stopRingbackForCall(Call call) {
-        ThreadUtil.checkOnMainThread();
-
         if (mCall == call) {
             // The foreground call is no longer dialing or is no longer the foreground call. In
             // either case, stop the ringback tone.
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index a4bcab0..05882ab 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -64,7 +64,6 @@
          * @param callback The callback to notify of the binding's success or failure.
          */
         void bind(BindCallback callback) {
-            ThreadUtil.checkOnMainThread();
             Log.d(ServiceBinder.this, "bind()");
 
             // Reset any abort request if we're asked to bind again.
@@ -105,37 +104,43 @@
     private final class ServiceBinderConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder binder) {
-            ThreadUtil.checkOnMainThread();
-            Log.i(this, "Service bound %s", componentName);
+            synchronized (mLock) {
+                Log.i(this, "Service bound %s", componentName);
 
-            // Unbind request was queued so unbind immediately.
-            if (mIsBindingAborted) {
-                clearAbort();
-                logServiceDisconnected("onServiceConnected");
-                mContext.unbindService(this);
-                handleFailedConnection();
-                return;
+                // Unbind request was queued so unbind immediately.
+                if (mIsBindingAborted) {
+                    clearAbort();
+                    logServiceDisconnected("onServiceConnected");
+                    mContext.unbindService(this);
+                    handleFailedConnection();
+                    return;
+                }
+
+                mServiceConnection = this;
+                setBinder(binder);
+                handleSuccessfulConnection();
             }
-
-            mServiceConnection = this;
-            setBinder(binder);
-            handleSuccessfulConnection();
         }
 
         @Override
         public void onServiceDisconnected(ComponentName componentName) {
-            logServiceDisconnected("onServiceDisconnected");
+            synchronized (mLock) {
+                logServiceDisconnected("onServiceDisconnected");
 
-            mServiceConnection = null;
-            clearAbort();
+                mServiceConnection = null;
+                clearAbort();
 
-            handleServiceDisconnected();
+                handleServiceDisconnected();
+            }
         }
     }
 
     /** The application context. */
     private final Context mContext;
 
+    /** The Telecom lock object. */
+    protected final TelecomSystem.SyncRoot mLock;
+
     /** The intent action to use when binding through {@link Context#bindService}. */
     private final String mServiceAction;
 
@@ -180,11 +185,12 @@
      * @param userHandle The {@link UserHandle} to use for binding.
      */
     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
-            UserHandle userHandle) {
+            TelecomSystem.SyncRoot lock, UserHandle userHandle) {
         Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
         Preconditions.checkNotNull(componentName);
 
         mContext = context;
+        mLock = lock;
         mServiceAction = serviceAction;
         mComponentName = componentName;
         mUserHandle = userHandle;
@@ -219,8 +225,6 @@
      * Unbinds from the service if already bound, no-op otherwise.
      */
     final void unbind() {
-        ThreadUtil.checkOnMainThread();
-
         if (mServiceConnection == null) {
             // We're not yet bound, so queue up an abort request.
             mIsBindingAborted = true;
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 9c0b707..b7b1500 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -57,364 +57,284 @@
     private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
             "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
 
-    /** The context. */
-    private Context mContext;
-
-    /**
-     * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
-     * request after sending. The main thread will notify the request when it is complete.
-     */
-    private static final class MainThreadRequest {
-        /** The result of the request that is run on the main thread */
-        public Object result;
-        /** Object that can be used to store non-integer arguments */
-        public Object arg;
-    }
-
-    /**
-     * A handler that processes messages on the main thread. Since many of the method calls are not
-     * thread safe this is needed to shuttle the requests from the inbound binder threads to the
-     * main thread.
-     */
-    private final class MainThreadHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.obj instanceof MainThreadRequest) {
-                MainThreadRequest request = (MainThreadRequest) msg.obj;
-                Object result = null;
-                switch (msg.what) {
-                    case MSG_SILENCE_RINGER:
-                        mCallsManager.getRinger().silence();
-                        break;
-                    case MSG_SHOW_CALL_SCREEN:
-                        mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
-                        break;
-                    case MSG_END_CALL:
-                        result = endCallInternal();
-                        break;
-                    case MSG_ACCEPT_RINGING_CALL:
-                        acceptRingingCallInternal();
-                        break;
-                    case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
-                        mCallsManager.getMissedCallNotifier().clearMissedCalls();
-                        break;
-                    case MSG_IS_TTY_SUPPORTED:
-                        result = mCallsManager.isTtySupported();
-                        break;
-                    case MSG_GET_CURRENT_TTY_MODE:
-                        result = mCallsManager.getCurrentTtyMode();
-                        break;
-                    case MSG_NEW_INCOMING_CALL:
-                        if (request.arg == null || !(request.arg instanceof Intent)) {
-                            Log.w(this, "Invalid new incoming call request");
-                            break;
-                        }
-                        CallIntentProcessor.processIncomingCallIntent(
-                                mCallsManager,
-                                (Intent) request.arg);
-                        break;
-                }
-
-                if (result != null) {
-                    request.result = result;
-                    synchronized(request) {
-                        request.notifyAll();
-                    }
-                }
-            }
-        }
-    }
-
-    private static final String TAG = com.android.server.telecom.TelecomServiceImpl.class.getSimpleName();
-
-    private static final String SERVICE_NAME = "telecom";
-
-    private static final int MSG_SILENCE_RINGER = 1;
-    private static final int MSG_SHOW_CALL_SCREEN = 2;
-    private static final int MSG_END_CALL = 3;
-    private static final int MSG_ACCEPT_RINGING_CALL = 4;
-    private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
-    private static final int MSG_IS_TTY_SUPPORTED = 6;
-    private static final int MSG_GET_CURRENT_TTY_MODE = 7;
-    private static final int MSG_NEW_INCOMING_CALL = 8;
-
-    private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
-
-    private final AppOpsManager mAppOpsManager;
-    private final UserManager mUserManager;
-    private final PackageManager mPackageManager;
-    private final TelecomBinderImpl mBinderImpl;
-    private final CallsManager mCallsManager;
-    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
-
-    public TelecomServiceImpl(
-            Context context,
-            CallsManager callsManager,
-            PhoneAccountRegistrar phoneAccountRegistrar) {
-        mContext = context;
-        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mBinderImpl = new TelecomBinderImpl();
-
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mPackageManager = mContext.getPackageManager();
-
-        mCallsManager = callsManager;
-        mPhoneAccountRegistrar = phoneAccountRegistrar;
-    }
-
-    public IBinder getBinder() {
-        return mBinderImpl;
-    }
-
-    /**
-     * Implementation of the ITelecomService interface.
-     * TODO: Reorganize this inner class to top of file.
-     */
-    class TelecomBinderImpl extends ITelecomService.Stub {
+    private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
         @Override
         public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
-            enforceReadPermission();
-            long token = Binder.clearCallingIdentity();
-            try {
-                PhoneAccountHandle defaultOutgoingPhoneAccount =
-                        mCallsManager.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount(uriScheme);
-                // Make sure that the calling user can see this phone account.
-                if (defaultOutgoingPhoneAccount != null
-                        && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
-                    Log.w(this, "No account found for the calling user");
-                    return null;
+            synchronized (mLock) {
+                enforceReadPermission();
+                long token = Binder.clearCallingIdentity();
+                try {
+                    PhoneAccountHandle defaultOutgoingPhoneAccount =
+                            mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+                    // Make sure that the calling user can see this phone account.
+                    if (defaultOutgoingPhoneAccount != null
+                            && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
+                        Log.w(this, "No account found for the calling user");
+                        return null;
+                    }
+                    return defaultOutgoingPhoneAccount;
+                } catch (Exception e) {
+                    Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+                    throw e;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
                 }
-                return defaultOutgoingPhoneAccount;
-            } catch (Exception e) {
-                Log.e(this, e, "getDefaultOutgoingPhoneAccount");
-                throw e;
-            } finally {
-                Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
         public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
-            try {
-                PhoneAccountHandle userSelectedOutgoingPhoneAccount =
-                        mCallsManager.getPhoneAccountRegistrar().getUserSelectedOutgoingPhoneAccount();
-                // Make sure that the calling user can see this phone account.
-                if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
-                    Log.w(this, "No account found for the calling user");
-                    return null;
+            synchronized (mLock) {
+                try {
+                    PhoneAccountHandle userSelectedOutgoingPhoneAccount =
+                            mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+                    // Make sure that the calling user can see this phone account.
+                    if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
+                        Log.w(this, "No account found for the calling user");
+                        return null;
+                    }
+                    return userSelectedOutgoingPhoneAccount;
+                } catch (Exception e) {
+                    Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
+                    throw e;
                 }
-                return userSelectedOutgoingPhoneAccount;
-            } catch (Exception e) {
-                Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
-                throw e;
             }
         }
 
         @Override
         public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
-            enforceModifyPermission();
-
-            try {
-                mCallsManager.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(accountHandle);
-            } catch (Exception e) {
-                Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
-                throw e;
+            synchronized (mLock) {
+                enforceModifyPermission();
+                try {
+                    mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
+                } catch (Exception e) {
+                    Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
+                    throw e;
+                }
             }
         }
 
         @Override
         public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
-            enforceReadPermission();
-            long token = Binder.clearCallingIdentity();
-            try {
-                return filterForAccountsVisibleToCaller(
-                        mCallsManager.getPhoneAccountRegistrar().getCallCapablePhoneAccounts());
-            } catch (Exception e) {
-                Log.e(this, e, "getCallCapablePhoneAccounts");
-                throw e;
-            } finally {
-                Binder.restoreCallingIdentity(token);
+            synchronized (mLock) {
+                enforceReadPermission();
+                long token = Binder.clearCallingIdentity();
+                try {
+                    return filterForAccountsVisibleToCaller(
+                            mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
+                } catch (Exception e) {
+                    Log.e(this, e, "getCallCapablePhoneAccounts");
+                    throw e;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
         }
 
         @Override
         public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
-            enforceReadPermission();
-            long token = Binder.clearCallingIdentity();
-            try {
-                return filterForAccountsVisibleToCaller(
-                        mCallsManager.getPhoneAccountRegistrar().getCallCapablePhoneAccounts(uriScheme));
-            } catch (Exception e) {
-                Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
-                throw e;
-            } finally {
-                Binder.restoreCallingIdentity(token);
+            synchronized (mLock) {
+                enforceReadPermission();
+                long token = Binder.clearCallingIdentity();
+                try {
+                    return filterForAccountsVisibleToCaller(
+                            mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
+                } catch (Exception e) {
+                    Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
+                    throw e;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
         }
 
         @Override
         public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
-            try {
-                return filterForAccountsVisibleToCaller(
-                        mCallsManager.getPhoneAccountRegistrar().getPhoneAccountsForPackage(packageName));
-            } catch (Exception e) {
-                Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
-                throw e;
+            synchronized (mLock) {
+                try {
+                    return filterForAccountsVisibleToCaller(
+                            mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
+                } catch (Exception e) {
+                    Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
+                    throw e;
+                }
             }
         }
 
         @Override
         public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
-            try {
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.w(this, "%s is not visible for the calling user", accountHandle);
-                    return null;
+            synchronized (mLock) {
+                try {
+                    if (!isVisibleToCaller(accountHandle)) {
+                        Log.w(this, "%s is not visible for the calling user", accountHandle);
+                        return null;
+                    }
+                    return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
+                } catch (Exception e) {
+                    Log.e(this, e, "getPhoneAccount %s", accountHandle);
+                    throw e;
                 }
-                return mCallsManager.getPhoneAccountRegistrar().getPhoneAccountInternal(accountHandle);
-            } catch (Exception e) {
-                Log.e(this, e, "getPhoneAccount %s", accountHandle);
-                throw e;
             }
         }
 
         @Override
         public int getAllPhoneAccountsCount() {
-            try {
-                // This list is pre-filtered for the calling user.
-                return getAllPhoneAccounts().size();
-            } catch (Exception e) {
-                Log.e(this, e, "getAllPhoneAccountsCount");
-                throw e;
+            synchronized (mLock) {
+                try {
+                    // This list is pre-filtered for the calling user.
+                    return getAllPhoneAccounts().size();
+                } catch (Exception e) {
+                    Log.e(this, e, "getAllPhoneAccountsCount");
+                    throw e;
+                }
             }
         }
 
         @Override
         public List<PhoneAccount> getAllPhoneAccounts() {
-            try {
-                List<PhoneAccount> allPhoneAccounts = mCallsManager.getPhoneAccountRegistrar().getAllPhoneAccounts();
-                List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
-                for (PhoneAccount phoneAccount : profilePhoneAccounts) {
-                    if (isVisibleToCaller(phoneAccount)) {
-                        profilePhoneAccounts.add(phoneAccount);
+            synchronized (mLock) {
+                try {
+                    List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar
+                            .getAllPhoneAccounts();
+                    List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(
+                            allPhoneAccounts.size());
+                    for (PhoneAccount phoneAccount : profilePhoneAccounts) {
+                        if (isVisibleToCaller(phoneAccount)) {
+                            profilePhoneAccounts.add(phoneAccount);
+                        }
                     }
+                    return profilePhoneAccounts;
+                } catch (Exception e) {
+                    Log.e(this, e, "getAllPhoneAccounts");
+                    throw e;
                 }
-                return profilePhoneAccounts;
-            } catch (Exception e) {
-                Log.e(this, e, "getAllPhoneAccounts");
-                throw e;
             }
         }
 
         @Override
         public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
-            try {
-                return filterForAccountsVisibleToCaller(
-                        mCallsManager.getPhoneAccountRegistrar().getAllPhoneAccountHandles());
-            } catch (Exception e) {
-                Log.e(this, e, "getAllPhoneAccounts");
-                throw e;
+            synchronized (mLock) {
+                try {
+                    return filterForAccountsVisibleToCaller(
+                            mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+                } catch (Exception e) {
+                    Log.e(this, e, "getAllPhoneAccounts");
+                    throw e;
+                }
             }
         }
 
         @Override
         public PhoneAccountHandle getSimCallManager() {
-            try {
-                PhoneAccountHandle accountHandle = mCallsManager.getPhoneAccountRegistrar().getSimCallManager();
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.w(this, "%s is not visible for the calling user", accountHandle);
-                    return null;
+            synchronized (mLock) {
+                try {
+                    PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+                    if (!isVisibleToCaller(accountHandle)) {
+                        Log.w(this, "%s is not visible for the calling user", accountHandle);
+                        return null;
+                    }
+                    return accountHandle;
+                } catch (Exception e) {
+                    Log.e(this, e, "getSimCallManager");
+                    throw e;
                 }
-                return accountHandle;
-            } catch (Exception e) {
-                Log.e(this, e, "getSimCallManager");
-                throw e;
             }
         }
 
         @Override
         public void setSimCallManager(PhoneAccountHandle accountHandle) {
-            enforceModifyPermission();
-
-            try {
-                mCallsManager.getPhoneAccountRegistrar().setSimCallManager(accountHandle);
-            } catch (Exception e) {
-                Log.e(this, e, "setSimCallManager");
-                throw e;
+            synchronized (mLock) {
+                enforceModifyPermission();
+                try {
+                    mPhoneAccountRegistrar.setSimCallManager(accountHandle);
+                } catch (Exception e) {
+                    Log.e(this, e, "setSimCallManager");
+                    throw e;
+                }
             }
         }
 
         @Override
         public List<PhoneAccountHandle> getSimCallManagers() {
-            enforceReadPermission();
-            long token = Binder.clearCallingIdentity();
-            try {
-                return filterForAccountsVisibleToCaller(
-                        mCallsManager.getPhoneAccountRegistrar().getConnectionManagerPhoneAccounts());
-            } catch (Exception e) {
-                Log.e(this, e, "getSimCallManagers");
-                throw e;
-            } finally {
-                Binder.restoreCallingIdentity(token);
+            synchronized (mLock) {
+                enforceReadPermission();
+                long token = Binder.clearCallingIdentity();
+                try {
+                    return filterForAccountsVisibleToCaller(
+                            mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
+                } catch (Exception e) {
+                    Log.e(this, e, "getSimCallManagers");
+                    throw e;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
         }
 
         @Override
         public void registerPhoneAccount(PhoneAccount account) {
-            try {
-                enforcePhoneAccountModificationForPackage(
-                        account.getAccountHandle().getComponentName().getPackageName());
-                if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
-                    enforceRegisterCallProviderPermission();
-                }
-                if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
-                    enforceRegisterSimSubscriptionPermission();
-                }
-                if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
-                    enforceRegisterConnectionManagerPermission();
-                }
-                if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
-                    enforceRegisterMultiUser();
-                }
-                enforceUserHandleMatchesCaller(account.getAccountHandle());
+            synchronized (mLock) {
+                try {
+                    enforcePhoneAccountModificationForPackage(
+                            account.getAccountHandle().getComponentName().getPackageName());
+                    if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
+                        enforceRegisterCallProviderPermission();
+                    }
+                    if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                        enforceRegisterSimSubscriptionPermission();
+                    }
+                    if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+                        enforceRegisterConnectionManagerPermission();
+                    }
+                    if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+                        enforceRegisterMultiUser();
+                    }
+                    enforceUserHandleMatchesCaller(account.getAccountHandle());
 
-                mPhoneAccountRegistrar.registerPhoneAccount(account);
+                    mPhoneAccountRegistrar.registerPhoneAccount(account);
 
-                // Broadcast an intent indicating the phone account which was registered.
-                long token = Binder.clearCallingIdentity();
-                Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
-                intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
-                        account.getAccountHandle());
-                Log.i(this, "Sending phone-account intent as user");
-                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                        PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
-                Binder.restoreCallingIdentity(token);
-            } catch (Exception e) {
-                Log.e(this, e, "registerPhoneAccount %s", account);
-                throw e;
+                    // Broadcast an intent indicating the phone account which was registered.
+                    long token = Binder.clearCallingIdentity();
+                    Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+                    intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                            account.getAccountHandle());
+                    Log.i(this, "Sending phone-account intent as user");
+                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                            PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
+                    Binder.restoreCallingIdentity(token);
+                } catch (Exception e) {
+                    Log.e(this, e, "registerPhoneAccount %s", account);
+                    throw e;
+                }
             }
         }
 
         @Override
         public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
-            try {
-                enforcePhoneAccountModificationForPackage(
-                        accountHandle.getComponentName().getPackageName());
-                enforceUserHandleMatchesCaller(accountHandle);
-                mCallsManager.getPhoneAccountRegistrar().unregisterPhoneAccount(accountHandle);
-            } catch (Exception e) {
-                Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
-                throw e;
+            synchronized (mLock) {
+                try {
+                    enforcePhoneAccountModificationForPackage(
+                            accountHandle.getComponentName().getPackageName());
+                    enforceUserHandleMatchesCaller(accountHandle);
+                    mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
+                } catch (Exception e) {
+                    Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
+                    throw e;
+                }
             }
         }
 
         @Override
         public void clearAccounts(String packageName) {
-            try {
-                enforcePhoneAccountModificationForPackage(packageName);
-                mCallsManager.getPhoneAccountRegistrar().clearAccounts(packageName, Binder.getCallingUserHandle());
-            } catch (Exception e) {
-                Log.e(this, e, "clearAccounts %s", packageName);
-                throw e;
+            synchronized (mLock) {
+                try {
+                    enforcePhoneAccountModificationForPackage(packageName);
+                    mPhoneAccountRegistrar
+                            .clearAccounts(packageName, Binder.getCallingUserHandle());
+                } catch (Exception e) {
+                    Log.e(this, e, "clearAccounts %s", packageName);
+                    throw e;
+                }
             }
         }
 
@@ -423,16 +343,18 @@
          */
         @Override
         public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
-            enforceReadPermissionOrDefaultDialer();
-            try {
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.w(this, "%s is not visible for the calling user", accountHandle);
-                    return false;
+            synchronized (mLock) {
+                enforceReadPermissionOrDefaultDialer();
+                try {
+                    if (!isVisibleToCaller(accountHandle)) {
+                        Log.w(this, "%s is not visible for the calling user", accountHandle);
+                        return false;
+                    }
+                    return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+                } catch (Exception e) {
+                    Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+                    throw e;
                 }
-                return mCallsManager.getPhoneAccountRegistrar().isVoiceMailNumber(accountHandle, number);
-            } catch (Exception e) {
-                Log.e(this, e, "getSubscriptionIdForPhoneAccount");
-                throw e;
             }
         }
 
@@ -441,21 +363,24 @@
          */
         @Override
         public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
-            enforceReadPermissionOrDefaultDialer();
-            try {
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.w(this, "%s is not visible for the calling user", accountHandle);
-                    return false;
-                }
+            synchronized (mLock) {
+                enforceReadPermissionOrDefaultDialer();
+                try {
+                    if (!isVisibleToCaller(accountHandle)) {
+                        Log.w(this, "%s is not visible for the calling user", accountHandle);
+                        return false;
+                    }
 
-                int subId = SubscriptionManager.getDefaultVoiceSubId();
-                if (accountHandle != null) {
-                    subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
+                    int subId = SubscriptionManager.getDefaultVoiceSubId();
+                    if (accountHandle != null) {
+                        subId = mPhoneAccountRegistrar
+                                .getSubscriptionIdForPhoneAccount(accountHandle);
+                    }
+                    return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
+                } catch (Exception e) {
+                    Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+                    throw e;
                 }
-                return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
-            } catch (Exception e) {
-                Log.e(this, e, "getSubscriptionIdForPhoneAccount");
-                throw e;
             }
         }
 
@@ -465,16 +390,19 @@
         @Override
         public String getLine1Number(PhoneAccountHandle accountHandle) {
             enforceReadPermissionOrDefaultDialer();
-            try {
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.w(this, "%s is not visible for the calling user", accountHandle);
-                    return null;
+            synchronized (mLock) {
+                try {
+                    if (!isVisibleToCaller(accountHandle)) {
+                        Log.w(this, "%s is not visible for the calling user", accountHandle);
+                        return null;
+                    }
+                    int subId =
+                            mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+                    return getTelephonyManager().getLine1NumberForSubscriber(subId);
+                } catch (Exception e) {
+                    Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+                    throw e;
                 }
-                int subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
-                return getTelephonyManager().getLine1NumberForSubscriber(subId);
-            } catch (Exception e) {
-                Log.e(this, e, "getSubscriptionIdForPhoneAccount");
-                throw e;
             }
         }
 
@@ -483,9 +411,10 @@
          */
         @Override
         public void silenceRinger() {
-            Log.d(this, "silenceRinger");
-            enforceModifyPermission();
-            sendRequestAsync(MSG_SILENCE_RINGER, 0);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                mCallsManager.getRinger().silence();
+            }
         }
 
         /**
@@ -493,6 +422,7 @@
          */
         @Override
         public ComponentName getDefaultPhoneApp() {
+            // No need to synchronize
             Resources resources = mContext.getResources();
             return new ComponentName(
                     resources.getString(R.string.ui_default_package),
@@ -504,12 +434,12 @@
          */
         @Override
         public boolean isInCall() {
-            enforceReadPermission();
-            // Do not use sendRequest() with this method since it could cause a deadlock with
-            // audio service, which we call into from the main thread: AudioManager.setMode().
-            final int callState = mCallsManager.getCallState();
-            return callState == TelephonyManager.CALL_STATE_OFFHOOK
-                    || callState == TelephonyManager.CALL_STATE_RINGING;
+            synchronized (mLock) {
+                enforceReadPermission();
+                final int callState = mCallsManager.getCallState();
+                return callState == TelephonyManager.CALL_STATE_OFFHOOK
+                        || callState == TelephonyManager.CALL_STATE_RINGING;
+            }
         }
 
         /**
@@ -517,8 +447,10 @@
          */
         @Override
         public boolean isRinging() {
-            enforceReadPermission();
-            return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+            synchronized (mLock) {
+                enforceReadPermission();
+                return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+            }
         }
 
         /**
@@ -526,7 +458,9 @@
          */
         @Override
         public int getCallState() {
-            return mCallsManager.getCallState();
+            synchronized (mLock) {
+                return mCallsManager.getCallState();
+            }
         }
 
         /**
@@ -534,8 +468,10 @@
          */
         @Override
         public boolean endCall() {
-            enforceModifyPermission();
-            return (boolean) sendRequest(MSG_END_CALL);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                return endCallInternal();
+            }
         }
 
         /**
@@ -543,8 +479,10 @@
          */
         @Override
         public void acceptRingingCall() {
-            enforceModifyPermission();
-            sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
+            synchronized (mLock) {
+                enforceModifyPermission();
+                acceptRingingCallInternal();
+            }
         }
 
         /**
@@ -552,8 +490,10 @@
          */
         @Override
         public void showInCallScreen(boolean showDialpad) {
-            enforceReadPermissionOrDefaultDialer();
-            sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
+            synchronized (mLock) {
+                enforceReadPermissionOrDefaultDialer();
+                mCallsManager.getInCallController().bringToForeground(showDialpad);
+            }
         }
 
         /**
@@ -561,8 +501,10 @@
          */
         @Override
         public void cancelMissedCallsNotification() {
-            enforceModifyPermissionOrDefaultDialer();
-            sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
+            synchronized (mLock) {
+                enforceModifyPermissionOrDefaultDialer();
+                mCallsManager.getMissedCallNotifier().clearMissedCalls();
+            }
         }
 
         /**
@@ -570,45 +512,50 @@
          */
         @Override
         public boolean handlePinMmi(String dialString) {
-            enforceModifyPermissionOrDefaultDialer();
+            synchronized (mLock) {
+                enforceModifyPermissionOrDefaultDialer();
 
-            // Switch identity so that TelephonyManager checks Telecom's permissions instead.
-            long token = Binder.clearCallingIdentity();
-            boolean retval = false;
-            try {
-                retval = getTelephonyManager().handlePinMmi(dialString);
-            } finally {
-                Binder.restoreCallingIdentity(token);
+                // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+                long token = Binder.clearCallingIdentity();
+                boolean retval = false;
+                try {
+                    retval = getTelephonyManager().handlePinMmi(dialString);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+
+                return retval;
             }
-
-            return retval;
         }
 
         /**
          * @see android.telecom.TelecomManager#handleMmi
          */
         @Override
-        public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+        public boolean handlePinMmiForPhoneAccount(
+                PhoneAccountHandle accountHandle,
                 String dialString) {
-            enforceModifyPermissionOrDefaultDialer();
+            synchronized (mLock) {
+                enforceModifyPermissionOrDefaultDialer();
 
-            if (!isVisibleToCaller(accountHandle)) {
-                Log.w(this, "%s is not visible for the calling user", accountHandle);
-                return false;
+                if (!isVisibleToCaller(accountHandle)) {
+                    Log.w(this, "%s is not visible for the calling user", accountHandle);
+                    return false;
+                }
+
+                // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+                long token = Binder.clearCallingIdentity();
+                boolean retval = false;
+                try {
+                    int subId = mPhoneAccountRegistrar
+                            .getSubscriptionIdForPhoneAccount(accountHandle);
+                    retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+
+                return retval;
             }
-
-            // Switch identity so that TelephonyManager checks Telecom's permissions instead.
-            long token = Binder.clearCallingIdentity();
-            boolean retval = false;
-            try {
-                int subId = mCallsManager.getPhoneAccountRegistrar()
-                        .getSubscriptionIdForPhoneAccount(accountHandle);
-                retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-
-            return retval;
         }
 
         /**
@@ -616,25 +563,27 @@
          */
         @Override
         public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
-            enforceModifyPermissionOrDefaultDialer();
+            synchronized (mLock) {
+                enforceModifyPermissionOrDefaultDialer();
 
-            if (!isVisibleToCaller(accountHandle)) {
-                Log.w(this, "%s is not visible for the calling user", accountHandle);
-                return null;
+                if (!isVisibleToCaller(accountHandle)) {
+                    Log.w(this, "%s is not visible for the calling user", accountHandle);
+                    return null;
+                }
+
+                // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+                long token = Binder.clearCallingIdentity();
+                String retval = "content://icc/adn/";
+                try {
+                    long subId = mPhoneAccountRegistrar
+                            .getSubscriptionIdForPhoneAccount(accountHandle);
+                    retval = retval + "subId/" + subId;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+
+                return Uri.parse(retval);
             }
-
-            // Switch identity so that TelephonyManager checks Telecom's permissions instead.
-            long token = Binder.clearCallingIdentity();
-            String retval = "content://icc/adn/";
-            try {
-                long subId = mCallsManager.getPhoneAccountRegistrar()
-                        .getSubscriptionIdForPhoneAccount(accountHandle);
-                retval = retval + "subId/" + subId;
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-
-            return Uri.parse(retval);
         }
 
         /**
@@ -642,8 +591,10 @@
          */
         @Override
         public boolean isTtySupported() {
-            enforceReadPermission();
-            return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
+            synchronized (mLock) {
+                enforceReadPermission();
+                return mCallsManager.isTtySupported();
+            }
         }
 
         /**
@@ -651,8 +602,10 @@
          */
         @Override
         public int getCurrentTtyMode() {
-            enforceReadPermission();
-            return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
+            synchronized (mLock) {
+                enforceReadPermission();
+                return mCallsManager.getCurrentTtyMode();
+            }
         }
 
         /**
@@ -660,28 +613,28 @@
          */
         @Override
         public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
-            Log.i(this, "Adding new incoming call with phoneAccountHandle %s", phoneAccountHandle);
-            if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
-                // TODO(sail): Add unit tests for adding incoming calls from a SIM call manager.
-                if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName(
-                        phoneAccountHandle.getComponentName())) {
-                    Log.v(this, "Allowing call manager to add incoming call with PSTN handle");
-                } else {
-                    mAppOpsManager.checkPackage(Binder.getCallingUid(),
+            synchronized (mLock) {
+                Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
+                        phoneAccountHandle);
+                if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
+                    mAppOpsManager.checkPackage(
+                            Binder.getCallingUid(),
                             phoneAccountHandle.getComponentName().getPackageName());
+
                     // Make sure it doesn't cross the UserHandle boundary
                     enforceUserHandleMatchesCaller(phoneAccountHandle);
-                }
 
-                Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
-                intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
-                intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
-                if (extras != null) {
-                    intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+                    Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
+                    intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+                    intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
+                    if (extras != null) {
+                        intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+                    }
+                    CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent);
+                } else {
+                    Log.w(this,
+                            "Null phoneAccountHandle. Ignoring request to add new incoming call");
                 }
-                sendRequestAsync(MSG_NEW_INCOMING_CALL, 0, intent);
-            } else {
-                Log.w(this, "Null phoneAccountHandle. Ignoring request to add new incoming call");
             }
         }
 
@@ -690,24 +643,28 @@
          */
         @Override
         public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
-            if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
-                    TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
-                mAppOpsManager.checkPackage(
-                        Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
+            synchronized (mLock) {
+                if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
+                        TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
+                    mAppOpsManager.checkPackage(
+                            Binder.getCallingUid(),
+                            phoneAccountHandle.getComponentName().getPackageName());
 
-                // Make sure it doesn't cross the UserHandle boundary
-                enforceUserHandleMatchesCaller(phoneAccountHandle);
+                    // Make sure it doesn't cross the UserHandle boundary
+                    enforceUserHandleMatchesCaller(phoneAccountHandle);
 
-                Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
-                intent.setClass(mContext, CallIntentProcessor.class);
-                intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtras(extras);
-                intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
-                intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
-                mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
-            } else {
-                Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
-                        + " to add new unknown call.");
+                    Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+                    intent.setClass(mContext, CallIntentProcessor.class);
+                    intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtras(extras);
+                    intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
+                    intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+                    mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
+                } else {
+                    Log.i(this,
+                            "Null phoneAccountHandle or not initiated by Telephony. " +
+                            "Ignoring request to add new unknown call.");
+                }
             }
         }
 
@@ -738,10 +695,38 @@
 
                 pw.println("PhoneAccountRegistrar: ");
                 pw.increaseIndent();
-                mCallsManager.getPhoneAccountRegistrar().dump(pw);
+                mPhoneAccountRegistrar.dump(pw);
                 pw.decreaseIndent();
             }
         }
+    };
+
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+    private UserManager mUserManager;
+    private PackageManager mPackageManager;
+    private CallsManager mCallsManager;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final TelecomSystem.SyncRoot mLock;
+
+    public TelecomServiceImpl(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            TelecomSystem.SyncRoot lock) {
+        mContext = context;
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mPackageManager = mContext.getPackageManager();
+
+        mCallsManager = callsManager;
+        mLock = lock;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+    }
+
+    public IBinder getBinder() {
+        return mBinderImpl;
     }
 
     //
@@ -753,7 +738,7 @@
             return false;
         }
 
-        return isVisibleToCaller(mCallsManager.getPhoneAccountRegistrar()
+        return isVisibleToCaller(mPhoneAccountRegistrar
                 .getPhoneAccountInternal(accountHandle));
     }
 
@@ -983,41 +968,4 @@
     private TelephonyManager getTelephonyManager() {
         return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
-
-    private MainThreadRequest sendRequestAsync(int command, int arg1) {
-        return sendRequestAsync(command, arg1, null);
-    }
-
-    private MainThreadRequest sendRequestAsync(int command, int arg1, Object arg) {
-        MainThreadRequest request = new MainThreadRequest();
-        request.arg = arg;
-        mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
-        return request;
-    }
-
-    /**
-     * Posts the specified command to be executed on the main thread, waits for the request to
-     * complete, and returns the result.
-     */
-    private Object sendRequest(int command) {
-        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
-            MainThreadRequest request = new MainThreadRequest();
-            mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
-            return request.result;
-        } else {
-            MainThreadRequest request = sendRequestAsync(command, 0);
-
-            // Wait for the request to complete
-            synchronized (request) {
-                while (request.result == null) {
-                    try {
-                        request.wait();
-                    } catch (InterruptedException e) {
-                        // Do nothing, go back and wait until the request is complete
-                    }
-                }
-            }
-            return request.result;
-        }
-    }
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 1506d27..22377c9 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -41,11 +43,20 @@
         TelecomSystem getTelecomSystem();
     }
 
+
+    /**
+     * Tagging interface for the object used for synchronizing multi-threaded operations in
+     * the Telecom system.
+     */
+    public interface SyncRoot {
+    }
+
     private static final IntentFilter USER_SWITCHED_FILTER =
             new IntentFilter(Intent.ACTION_USER_SWITCHED);
 
     private static TelecomSystem INSTANCE = null;
 
+    private final SyncRoot mLock = new SyncRoot() { };
     private final MissedCallNotifier mMissedCallNotifier;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallsManager mCallsManager;
@@ -55,6 +66,7 @@
     private final CallIntentProcessor mCallIntentProcessor;
     private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
     private final TelecomServiceImpl mTelecomServiceImpl;
+    private final ContactsAsyncHelper mContactsAsyncHelper;
 
     private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
         @Override
@@ -87,46 +99,36 @@
 
         mMissedCallNotifier = missedCallNotifier;
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
+        mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
 
         mCallsManager = new CallsManager(
                 mContext,
+                mLock,
+                mContactsAsyncHelper,
                 mMissedCallNotifier,
                 mPhoneAccountRegistrar,
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory);
 
-        mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager);
+        mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
 
-        mMissedCallNotifier.setCallsManager(mCallsManager);
-
         mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
-        mBluetoothPhoneServiceImpl =
-                new BluetoothPhoneServiceImpl(mContext, mCallsManager, mPhoneAccountRegistrar);
+        mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
+                mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
         mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
         mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
                 mContext, mCallsManager);
-        mTelecomServiceImpl =
-                new TelecomServiceImpl(mContext, mCallsManager, mPhoneAccountRegistrar);
+        mTelecomServiceImpl = new TelecomServiceImpl(
+                mContext, mCallsManager, mPhoneAccountRegistrar, mLock);
     }
 
-    public MissedCallNotifier getMissedCallNotifier() {
-        return mMissedCallNotifier;
-    }
-
+    @VisibleForTesting
     public PhoneAccountRegistrar getPhoneAccountRegistrar() {
         return mPhoneAccountRegistrar;
     }
 
-    public CallsManager getCallsManager() {
-        return mCallsManager;
-    }
-
-    public RespondViaSmsManager getRespondViaSmsManager() {
-        return mRespondViaSmsManager;
-    }
-
     public BluetoothPhoneServiceImpl getBluetoothPhoneServiceImpl() {
         return mBluetoothPhoneServiceImpl;
     }
@@ -142,4 +144,8 @@
     public TelecomServiceImpl getTelecomServiceImpl() {
         return mTelecomServiceImpl;
     }
+
+    public Object getLock() {
+        return mLock;
+    }
 }
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index a130522..04f08f9 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -60,7 +60,7 @@
         return pstnComponentName.equals(componentName);
     }
 
-    static boolean shouldProcessAsEmergency(Context context, Uri handle) {
+    public static boolean shouldProcessAsEmergency(Context context, Uri handle) {
         return handle != null && PhoneNumberUtils.isPotentialLocalEmergencyNumber(
                 context, handle.getSchemeSpecificPart());
     }
diff --git a/src/com/android/server/telecom/components/BluetoothPhoneService.java b/src/com/android/server/telecom/components/BluetoothPhoneService.java
index 1f757d5..c5e195c 100644
--- a/src/com/android/server/telecom/components/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/components/BluetoothPhoneService.java
@@ -30,7 +30,9 @@
 
     @Override
     public IBinder onBind(Intent intent) {
-        return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+        synchronized (getTelecomSystem().getLock()) {
+            return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+        }
     }
 
     @Override
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
index 8e1f72d..5198862 100644
--- a/src/com/android/server/telecom/components/PrimaryCallReceiver.java
+++ b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
@@ -1,7 +1,6 @@
 package com.android.server.telecom.components;
 
 import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.UserCallIntentProcessor;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -17,7 +16,9 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+        synchronized (getTelecomSystem().getLock()) {
+            getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+        }
     }
 
     @Override
diff --git a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
index b4f525c..e0ca3c4 100644
--- a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
@@ -32,7 +32,9 @@
     /** {@inheritDoc} */
     @Override
     public void onReceive(Context context, Intent intent) {
-        getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+        synchronized (getTelecomSystem().getLock()) {
+            getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+        }
     }
 
     @Override
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 8d5190b..49b4aa4 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -41,39 +41,54 @@
     @Override
     public IBinder onBind(Intent intent) {
         Log.d(this, "onBind");
-        // We are guaranteed that the TelecomService will be started before any other
-        // components in this package because it is started and kept running by the system.
-        TelecomSystem.setInstance(
-                new TelecomSystem(
-                        this,
-                        new MissedCallNotifierImpl(getApplicationContext()),
-                        new HeadsetMediaButtonFactory() {
-                            @Override
-                            public HeadsetMediaButton create(Context context,
-                                    CallsManager callsManager) {
-                                return new HeadsetMediaButton(context, callsManager);
-                            }
-                        },
-                        new ProximitySensorManagerFactory() {
-                            @Override
-                            public ProximitySensorManager create(
-                                    Context context,
-                                    CallsManager callsManager) {
-                                return new ProximitySensorManager(context, callsManager);
-                            }
-                        },
-                        new InCallWakeLockControllerFactory() {
-                            @Override
-                            public InCallWakeLockController create(Context context,
-                                    CallsManager callsManager) {
-                                return new InCallWakeLockController(context, callsManager);
-                            }
-                        }));
-        // Start the BluetoothPhoneService
-        if (BluetoothAdapter.getDefaultAdapter() != null) {
-            startService(new Intent(this, BluetoothPhoneService.class));
+        initializeTelecomSystem(this);
+        synchronized (getTelecomSystem().getLock()) {
+            return getTelecomSystem().getTelecomServiceImpl().getBinder();
         }
-        return getTelecomSystem().getTelecomServiceImpl().getBinder();
+    }
+
+    /**
+     * This method is to be called by components (Activitys, Services, ...) to initialize the
+     * Telecom singleton. It should only be called on the main thread. As such, it is atomic
+     * and needs no synchronization -- it will either perform its initialization, after which
+     * the {@link TelecomSystem#getInstance()} will be initialized, or some other invocation of
+     * this method on the main thread will have happened strictly prior to it, and this method
+     * will be a benign no-op.
+     *
+     * @param context
+     */
+    static void initializeTelecomSystem(Context context) {
+        if (TelecomSystem.getInstance() == null) {
+            TelecomSystem.setInstance(
+                    new TelecomSystem(
+                            context,
+                            new MissedCallNotifierImpl(context.getApplicationContext()),
+                            new HeadsetMediaButtonFactory() {
+                                @Override
+                                public HeadsetMediaButton create(Context context,
+                                        CallsManager callsManager) {
+                                    return new HeadsetMediaButton(context, callsManager);
+                                }
+                            },
+                            new ProximitySensorManagerFactory() {
+                                @Override
+                                public ProximitySensorManager create(
+                                        Context context,
+                                        CallsManager callsManager) {
+                                    return new ProximitySensorManager(context, callsManager);
+                                }
+                            },
+                            new InCallWakeLockControllerFactory() {
+                                @Override
+                                public InCallWakeLockController create(Context context,
+                                        CallsManager callsManager) {
+                                    return new InCallWakeLockController(context, callsManager);
+                                }
+                            }));
+        }
+        if (BluetoothAdapter.getDefaultAdapter() != null) {
+            context.startService(new Intent(context, BluetoothPhoneService.class));
+        }
     }
 
     @Override
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 3ea92bd..ccff468 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -18,7 +18,7 @@
 
 import com.android.server.telecom.CallIntentProcessor;
 import com.android.server.telecom.Log;
-import com.android.server.telecom.UserCallIntentProcessor;
+import com.android.server.telecom.TelecomSystem;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -41,10 +41,10 @@
  * {@link TelecomManager#getDefaultPhoneApp()} will also be granted the ability to
  * make emergency outgoing calls using the CALL action. In order to do this, it must call
  * startActivityForResult on the CALL intent to allow its package name to be passed to
- * {@link UserCallActivity}. Calling startActivity will continue to work on all non-emergency numbers
- * just like it did pre-L.
+ * {@link UserCallActivity}. Calling startActivity will continue to work on all non-emergency
+ * numbers just like it did pre-L.
  */
-public class UserCallActivity extends Activity {
+public class UserCallActivity extends Activity implements TelecomSystem.Component {
 
     @Override
     protected void onCreate(Bundle bundle) {
@@ -67,4 +67,9 @@
             }
         }
     }
+
+    @Override
+    public TelecomSystem getTelecomSystem() {
+        return TelecomSystem.getInstance();
+    }
 }
diff --git a/src/com/android/server/telecom/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
similarity index 95%
rename from src/com/android/server/telecom/UserCallIntentProcessor.java
rename to src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 36d2126..97a3497 100644
--- a/src/com/android/server/telecom/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.telecom;
+package com.android.server.telecom.components;
 
-import com.android.server.telecom.components.PrimaryCallReceiver;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelephonyUtil;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index cd62d91..9022d27 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -20,10 +20,12 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
 import com.android.server.telecom.Constants;
-import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.ContactsAsyncHelper;
 import com.android.server.telecom.Log;
+import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.R;
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.TelecomSystem;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -61,8 +63,7 @@
  *     direct reference to the CallsManager. Try to make this class simply handle the UI
  *     and Android-framework entanglements of missed call notification.
  */
-public class MissedCallNotifierImpl extends CallsManagerListenerBase implements
-        MissedCallNotifier {
+public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
 
     private static final String[] CALL_LOG_PROJECTION = new String[] {
         Calls._ID,
@@ -83,7 +84,6 @@
     private static final int MISSED_CALL_NOTIFICATION_ID = 1;
 
     private final Context mContext;
-    private CallsManager mCallsManager;
     private final NotificationManager mNotificationManager;
 
     // Used to track the number of missed calls.
@@ -93,12 +93,6 @@
         mContext = context;
         mNotificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        updateOnStartup();
-    }
-
-    public void setCallsManager(CallsManager callsManager) {
-        this.mCallsManager = callsManager;
     }
 
     /** {@inheritDoc} */
@@ -300,7 +294,11 @@
     /**
      * Adds the missed call notification on startup if there are unread missed calls.
      */
-    private void updateOnStartup() {
+    @Override
+    public void updateOnStartup(
+            final TelecomSystem.SyncRoot lock,
+            final CallsManager callsManager,
+            final ContactsAsyncHelper contactsAsyncHelper) {
         Log.d(this, "updateOnStartup()...");
 
         // instantiate query handler
@@ -327,28 +325,33 @@
                                                 handleString, null);
                             }
 
-                            // Convert the data to a call object
-                            Call call = new Call(mContext, mCallsManager,
-                                    null, null, null, null, null, true,
-                                    false);
-                            call.setDisconnectCause(new DisconnectCause(DisconnectCause.MISSED));
-                            call.setState(CallState.DISCONNECTED);
-                            call.setCreationTimeMillis(date);
+                            synchronized (lock) {
 
-                            // Listen for the update to the caller information before posting the
-                            // notification so that we have the contact info and photo.
-                            call.addListener(new Call.ListenerBase() {
-                                @Override
-                                public void onCallerInfoChanged(Call call) {
-                                    call.removeListener(this);  // No longer need to listen to call
-                                                                // changes after the contact info
-                                                                // is retrieved.
-                                    showMissedCallNotification(call);
-                                }
-                            });
-                            // Set the handle here because that is what triggers the contact info
-                            // query.
-                            call.setHandle(handle, presentation);
+                                // Convert the data to a call object
+                                Call call = new Call(mContext, callsManager,
+                                        null, contactsAsyncHelper, null, null, null, null, true,
+                                        false);
+                                call.setDisconnectCause(
+                                        new DisconnectCause(DisconnectCause.MISSED));
+                                call.setState(CallState.DISCONNECTED);
+                                call.setCreationTimeMillis(date);
+
+                                // Listen for the update to the caller information before posting
+                                // the notification so that we have the contact info and photo.
+                                call.addListener(new Call.ListenerBase() {
+                                    @Override
+                                    public void onCallerInfoChanged(Call call) {
+                                        call.removeListener(
+                                                this);  // No longer need to listen to call
+                                        // changes after the contact info
+                                        // is retrieved.
+                                        showMissedCallNotification(call);
+                                    }
+                                });
+                                // Set the handle here because that is what triggers the contact
+                                // info query.
+                                call.setHandle(handle, presentation);
+                            }
                         }
                     } finally {
                         cursor.close();
diff --git a/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java b/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java
index 8e19a55..0b5843d 100644
--- a/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java
+++ b/tests/src/com/android/server/telecom/tests/SimpleTelecomTest.java
@@ -80,14 +80,10 @@
     ///////////////////////////////////////////////////////////////////////////
     // Telecom specific mock objects
 
-    @Mock
-    MissedCallNotifier mMissedCallNotifier;
-    @Mock
-    HeadsetMediaButtonFactory mHeadsetMediaButtonFactory;
-    @Mock
-    ProximitySensorManagerFactory mProximitySensorManagerFactory;
-    @Mock
-    InCallWakeLockControllerFactory mInCallWakeLockControllerFactory;
+    @Mock MissedCallNotifier mMissedCallNotifier;
+    @Mock HeadsetMediaButtonFactory mHeadsetMediaButtonFactory;
+    @Mock ProximitySensorManagerFactory mProximitySensorManagerFactory;
+    @Mock InCallWakeLockControllerFactory mInCallWakeLockControllerFactory;
     @Mock HeadsetMediaButton mHeadsetMediaButton;
     @Mock ProximitySensorManager mProximitySensorManager;
     @Mock InCallWakeLockController mInCallWakeLockController;
@@ -184,18 +180,13 @@
         when(mInCallService.queryLocalInterface(anyString()))
                 .thenReturn(mInCallService);
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                mSystem = new TelecomSystem(
-                        mContextHolder.getTestDouble(),
-                        mMissedCallNotifier,
-                        mHeadsetMediaButtonFactory,
-                        mProximitySensorManagerFactory,
-                        mInCallWakeLockControllerFactory);
-                mSystem.getPhoneAccountRegistrar().registerPhoneAccount(mTestPhoneAccount);
-            }
-        });
+        mSystem = new TelecomSystem(
+                mContextHolder.getTestDouble(),
+                mMissedCallNotifier,
+                mHeadsetMediaButtonFactory,
+                mProximitySensorManagerFactory,
+                mInCallWakeLockControllerFactory);
+        mSystem.getPhoneAccountRegistrar().registerPhoneAccount(mTestPhoneAccount);
     }
 
     @Override
@@ -251,21 +242,16 @@
             }
         }).when(mInCallService).updateCall((ParcelableCall) any());
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                // Start an outgoing phone call
-                String number = "650-555-1212";
-                Intent actionCallIntent = new Intent();
-                actionCallIntent.setData(Uri.parse("tel:" + number));
-                actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
-                actionCallIntent.putExtra(
-                        TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
-                        mTestPhoneAccount.getAccountHandle());
-                actionCallIntent.setAction(Intent.ACTION_CALL);
-                mSystem.getCallIntentProcessor().processIntent(actionCallIntent);
-            }
-        });
+        // Start an outgoing phone call
+        String number = "650-555-1212";
+        Intent actionCallIntent = new Intent();
+        actionCallIntent.setData(Uri.parse("tel:" + number));
+        actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
+        actionCallIntent.putExtra(
+                TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                mTestPhoneAccount.getAccountHandle());
+        actionCallIntent.setAction(Intent.ACTION_CALL);
+        mSystem.getCallIntentProcessor().processIntent(actionCallIntent);
 
         // Sanity check that the in-call adapter is now set
         assertNotNull(mIInCallAdapter);
@@ -297,28 +283,18 @@
                 anyBoolean());
 
         // Pass on the new outgoing call Intent
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
-                mNewOutgoingCallReceiver.setPendingResult(
-                        new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0));
-                mNewOutgoingCallReceiver.setResultData(
-                        mNewOutgoingCallIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
-                mNewOutgoingCallReceiver.onReceive(
-                        mContextHolder.getTestDouble(),
-                        mNewOutgoingCallIntent);
-            }
-        });
+        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
+        mNewOutgoingCallReceiver.setPendingResult(
+                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0));
+        mNewOutgoingCallReceiver.setResultData(
+                mNewOutgoingCallIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+        mNewOutgoingCallReceiver.onReceive(
+                mContextHolder.getTestDouble(),
+                mNewOutgoingCallIntent);
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                assertNotNull(mConnectionServiceAdapter);
-                assertNotNull(mConnectionRequest);
-                assertNotNull(mConnectionId);
-            }
-        });
+        assertNotNull(mConnectionServiceAdapter);
+        assertNotNull(mConnectionRequest);
+        assertNotNull(mConnectionId);
 
         mConnectionServiceAdapter.handleCreateConnectionComplete(
                 mConnectionId,
@@ -341,76 +317,28 @@
         mConnectionServiceAdapter.setDialing(mConnectionId);
         mConnectionServiceAdapter.setActive(mConnectionId);
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                assertNotNull(mParcelableCall);
-                assertEquals(CallState.ACTIVE, mParcelableCall.getState());
-            }
-        });
+        assertNotNull(mParcelableCall);
+        assertEquals(CallState.ACTIVE, mParcelableCall.getState());
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mIInCallAdapter.disconnectCall(mParcelableCall.getId());
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
+        try {
+            mIInCallAdapter.disconnectCall(mParcelableCall.getId());
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                assertNotNull(mParcelableCall);
-                assertEquals(CallState.ACTIVE, mParcelableCall.getState());
-                try {
-                    verify(mConnectionService).disconnect(mConnectionId);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
+        assertNotNull(mParcelableCall);
+        assertEquals(CallState.ACTIVE, mParcelableCall.getState());
+        try {
+            verify(mConnectionService).disconnect(mConnectionId);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
 
         mConnectionServiceAdapter.setDisconnected(
                 mConnectionId,
                 new DisconnectCause(DisconnectCause.LOCAL));
 
-        runOnMainThreadAndWait(new Runnable() {
-            @Override
-            public void run() {
-                assertEquals(CallState.DISCONNECTED, mParcelableCall.getState());
-            }
-        });
-    }
-
-    private void runOnMainThreadAndWait(Runnable task) {
-        runOn(mMainLooper, task);
-    }
-
-    private void runOnTestThreadAndWait(Runnable task) {
-        runOn(mTestLooper, task);
-    }
-
-    private  void runOn(Looper looper, final Runnable task) {
-        final Object lock = new Object();
-        synchronized (lock) {
-            new Handler(looper).post(new Runnable() {
-                @Override
-                public void run() {
-                    task.run();
-                    synchronized (lock) {
-                        lock.notifyAll();
-                    }
-                }
-            });
-            try {
-                lock.wait();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
+        assertEquals(CallState.DISCONNECTED, mParcelableCall.getState());
     }
 
     private String exceptionToString(Throwable t) {