Refactoring for testability

Refactor to a single app singleton, TelecomSystem, which owns the
intialization of the rest of the app.

Change-Id: I3036f200b56f710e7f830b469260a67145c327e7
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a2c0b91..9a47d30 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -75,7 +75,7 @@
              contain contact information in the intent's data. CallActivity handles any data
              URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
              contacts provider entries. Any data not fitting the schema described is ignored. -->
-        <activity android:name="CallActivity"
+        <activity android:name=".components.UserCallActivity"
                 android:theme="@style/Theme.Telecomm.Transparent"
                 android:permission="android.permission.CALL_PHONE"
                 android:excludeFromRecents="true"
@@ -114,7 +114,7 @@
              processed. High priority of 1000 is used in all intent filters to prevent anything but
              the system from processing this intent (b/8871505). -->
         <activity-alias android:name="PrivilegedCallActivity"
-                android:targetActivity="CallActivity"
+                android:targetActivity=".components.UserCallActivity"
                 android:permission="android.permission.CALL_PRIVILEGED"
                 android:process=":ui">
             <intent-filter android:priority="1000">
@@ -149,7 +149,7 @@
         <!-- TODO: Is there really a notion of an emergency SIP number? If not, can
              that scheme be removed from this activity? -->
         <activity-alias android:name="EmergencyCallActivity"
-                android:targetActivity="CallActivity"
+                android:targetActivity=".components.UserCallActivity"
                 android:permission="android.permission.CALL_PRIVILEGED"
                 android:process=":ui">
             <intent-filter android:priority="1000">
@@ -176,7 +176,7 @@
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name="TelecomBroadcastReceiver" android:exported="false"
+        <receiver android:name=".components.TelecomBroadcastReceiver" android:exported="false"
                 android:process="system">
             <intent-filter>
                 <action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
@@ -185,7 +185,7 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="PhoneAccountBroadcastReceiver"
+        <receiver android:name=".components.PhoneAccountBroadcastReceiver"
                 android:process="system">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
@@ -203,7 +203,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".ErrorDialogActivity"
+        <activity android:name=".components.ErrorDialogActivity"
                 android:configChanges="orientation|screenSize|keyboardHidden"
                 android:excludeFromRecents="true"
                 android:launchMode="singleInstance"
@@ -211,13 +211,13 @@
                 android:process=":ui">
         </activity>
 
-        <receiver android:name=".CallReceiver"
+        <receiver android:name=".components.PrimaryCallReceiver"
                 android:exported="true"
                 android:permission="android.permission.MODIFY_PHONE_STATE"
                 android:process="system">
         </receiver>
 
-        <service android:name="BluetoothPhoneService"
+        <service android:name=".components.BluetoothPhoneService"
                 android:singleUser="true"
                 android:process="system">
             <intent-filter>
@@ -225,7 +225,7 @@
             </intent-filter>
         </service>
 
-        <service android:name=".TelecomService"
+        <service android:name=".components.TelecomService"
                 android:singleUser="true"
                 android:process="system">
             <intent-filter>
diff --git a/src/com/android/server/telecom/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
similarity index 92%
rename from src/com/android/server/telecom/BluetoothPhoneService.java
rename to src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 9a0cae1..e65d633 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom;
 
-import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
@@ -49,7 +48,7 @@
  * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
  * and accepts call-related commands to perform on behalf of the BT device.
  */
-public final class BluetoothPhoneService extends Service {
+public final class BluetoothPhoneServiceImpl {
     /**
      * Request object for performing synchronous requests to the main thread.
      */
@@ -204,7 +203,7 @@
         public void handleMessage(Message msg) {
             MainThreadRequest request = msg.obj instanceof MainThreadRequest ?
                     (MainThreadRequest) msg.obj : null;
-            CallsManager callsManager = getCallsManager();
+            CallsManager callsManager = mCallsManager;
             Call call = null;
 
             Log.d(TAG, "handleMessage(%d) w/ param %s",
@@ -215,7 +214,7 @@
                     try {
                         call = callsManager.getRingingCall();
                         if (call != null) {
-                            getCallsManager().answerCall(call, 0);
+                            mCallsManager.answerCall(call, 0);
                         }
                     } finally {
                         request.setResult(call != null);
@@ -268,7 +267,7 @@
                         }
 
                         if (TextUtils.isEmpty(address)) {
-                            address = TelephonyManager.from(BluetoothPhoneService.this)
+                            address = TelephonyManager.from(mContext)
                                     .getLine1Number();
                         }
                     } finally {
@@ -284,7 +283,7 @@
                             label = account.getLabel().toString();
                         } else {
                             // Finally, just get the network name from telephony.
-                            label = TelephonyManager.from(BluetoothPhoneService.this)
+                            label = TelephonyManager.from(mContext)
                                     .getNetworkOperatorName();
                         }
                     } finally {
@@ -337,7 +336,7 @@
             // When the call later transitions to DIALING/DISCONNECTED we will then send out the
             // aggregated update.
             if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
-                for (Call otherCall : CallsManager.getInstance().getCalls()) {
+                for (Call otherCall : mCallsManager.getCalls()) {
                     if (otherCall.getState() == CallState.CONNECTING) {
                         return;
                     }
@@ -347,7 +346,7 @@
             // To have an active call and another dialing at the same time is an invalid BT
             // state. We can assume that the active call will be automatically held which will
             // send another update at which point we will be in the right state.
-            if (CallsManager.getInstance().getActiveCall() != null
+            if (mCallsManager.getActiveCall() != null
                     && oldState == CallState.CONNECTING && newState == CallState.DIALING) {
                 return;
             }
@@ -431,49 +430,40 @@
 
     private boolean mHeadsetUpdatedRecently = false;
 
-    public BluetoothPhoneService() {
-        Log.v(TAG, "Constructor");
-    }
+    private Context mContext;
+    private CallsManager mCallsManager;
+    private PhoneAccountRegistrar mPhoneAccountRegistrar;
 
-    public static final void start(Context context) {
-        if (BluetoothAdapter.getDefaultAdapter() != null) {
-            context.startService(new Intent(context, BluetoothPhoneService.class));
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.d(TAG, "Binding service");
+    public IBinder getBinder() {
         return mBinder;
     }
 
-    @Override
-    public void onCreate() {
-        Log.d(TAG, "onCreate");
+    public BluetoothPhoneServiceImpl(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar) {
+        Log.d(this, "onCreate");
+
+        mContext = context;
+        mCallsManager = callsManager;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
 
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mBluetoothAdapter == null) {
-            Log.d(TAG, "BluetoothPhoneService shutting down, no BT Adapter found.");
+            Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
             return;
         }
-        mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+        mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
 
         IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-        registerReceiver(mBluetoothAdapterReceiver, intentFilter);
+        context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
 
-        CallsManager.getInstance().addListener(mCallsManagerListener);
+        mCallsManager.addListener(mCallsManagerListener);
         updateHeadsetWithCallState(false /* force */);
     }
 
-    @Override
-    public void onDestroy() {
-        Log.d(TAG, "onDestroy");
-        CallsManager.getInstance().removeListener(mCallsManagerListener);
-        super.onDestroy();
-    }
-
     private boolean processChld(int chld) {
-        CallsManager callsManager = CallsManager.getInstance();
+        CallsManager callsManager = mCallsManager;
         Call activeCall = callsManager.getActiveCall();
         Call ringingCall = callsManager.getRingingCall();
         Call heldCall = callsManager.getHeldCall();
@@ -533,7 +523,8 @@
     }
 
     private void enforceModifyPermission() {
-        enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE, null);
     }
 
     private <T> T sendSynchronousRequest(int message) {
@@ -566,7 +557,7 @@
     }
 
     private void sendListOfCalls(boolean shouldLog) {
-        Collection<Call> mCalls = getCallsManager().getCalls();
+        Collection<Call> mCalls = mCallsManager.getCalls();
         for (Call call : mCalls) {
             // We don't send the parent conference call to the bluetooth device.
             if (!call.isConference()) {
@@ -580,7 +571,7 @@
      * Sends a single clcc (C* List Current Calls) event for the specified call.
      */
     private void sendClccForCall(Call call, boolean shouldLog) {
-        boolean isForeground = getCallsManager().getForegroundCall() == call;
+        boolean isForeground = mCallsManager.getForegroundCall() == call;
         int state = convertCallState(call.getState(), isForeground);
         boolean isPartOfConference = false;
 
@@ -678,7 +669,7 @@
      *      changed.
      */
     private void updateHeadsetWithCallState(boolean force) {
-        CallsManager callsManager = getCallsManager();
+        CallsManager callsManager = mCallsManager;
         Call activeCall = callsManager.getActiveCall();
         Call ringingCall = callsManager.getRingingCall();
         Call heldCall = callsManager.getHeldCall();
@@ -791,7 +782,7 @@
     }
 
     private int getBluetoothCallStateForUpdate() {
-        CallsManager callsManager = getCallsManager();
+        CallsManager callsManager = mCallsManager;
         Call ringingCall = callsManager.getRingingCall();
         Call dialingCall = callsManager.getDialingCall();
 
@@ -848,33 +839,28 @@
         return CALL_STATE_IDLE;
     }
 
-    private CallsManager getCallsManager() {
-        return CallsManager.getInstance();
-    }
-
     /**
      * Returns the best phone account to use for the given state of all calls.
      * First, tries to return the phone account for the foreground call, second the default
      * phone account for PhoneAccount.SCHEME_TEL.
      */
     private PhoneAccount getBestPhoneAccount() {
-        PhoneAccountRegistrar registry = TelecomGlobals.getInstance().getPhoneAccountRegistrar();
-        if (registry == null) {
+        if (mPhoneAccountRegistrar == null) {
             return null;
         }
 
-        Call call = getCallsManager().getForegroundCall();
+        Call call = mCallsManager.getForegroundCall();
 
         PhoneAccount account = null;
         if (call != null) {
             // First try to get the network name of the foreground call.
-            account = registry.getPhoneAccount(call.getTargetPhoneAccount());
+            account = mPhoneAccountRegistrar.getPhoneAccount(call.getTargetPhoneAccount());
         }
 
         if (account == null) {
             // Second, Try to get the label for the default Phone Account.
-            account = registry.getPhoneAccount(
-                    registry.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
+            account = mPhoneAccountRegistrar.getPhoneAccount(
+                    mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL));
         }
         return account;
     }
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 4e081ee..464975f 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -301,6 +301,7 @@
     private StatusHints mStatusHints;
     private final ConnectionServiceRepository mRepository;
     private final Context mContext;
+    private final CallsManager mCallsManager;
 
     private boolean mWasConferencePreviouslyMerged = false;
 
@@ -328,6 +329,7 @@
      */
     Call(
             Context context,
+            CallsManager callsManager,
             ConnectionServiceRepository repository,
             Uri handle,
             GatewayInfo gatewayInfo,
@@ -337,6 +339,7 @@
             boolean isConference) {
         mState = isConference ? CallState.ACTIVE : CallState.NEW;
         mContext = context;
+        mCallsManager = callsManager;
         mRepository = repository;
         setHandle(handle);
         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
@@ -365,6 +368,7 @@
      */
     Call(
             Context context,
+            CallsManager callsManager,
             ConnectionServiceRepository repository,
             Uri handle,
             GatewayInfo gatewayInfo,
@@ -373,8 +377,9 @@
             boolean isIncoming,
             boolean isConference,
             long connectTimeMillis) {
-        this(context, repository, handle, gatewayInfo, connectionManagerPhoneAccountHandle,
-                targetPhoneAccountHandle, isIncoming, isConference);
+        this(context, callsManager, repository, handle, gatewayInfo,
+                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
+                isConference);
 
         mConnectTimeMillis = connectTimeMillis;
     }
@@ -822,7 +827,7 @@
     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
         clearConnectionService();
         setDisconnectCause(disconnectCause);
-        CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
+        mCallsManager.markCallAsDisconnected(this, disconnectCause);
 
         if (mIsUnknown) {
             for (Listener listener : mListeners) {
@@ -1316,7 +1321,7 @@
         if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
             Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
             mCannedSmsResponsesLoadingStarted = true;
-            RespondViaSmsManager.getInstance().loadCannedTextMessages(
+            TelecomSystem.getInstance().getRespondViaSmsManager().loadCannedTextMessages(
                     new Response<Void, List<String>>() {
                         @Override
                         public void onResult(Void request, List<String>... result) {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 9daf815..53b6261 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -78,7 +78,7 @@
     public void onCallRemoved(Call call) {
         // If we didn't already have focus, there's nothing to do.
         if (hasFocus()) {
-            if (CallsManager.getInstance().getCalls().isEmpty()) {
+            if (TelecomSystem.getInstance().getCallsManager().getCalls().isEmpty()) {
                 Log.v(this, "all calls removed, reseting system audio to default state");
                 setInitialAudioState(null, false /* force */);
                 mWasSpeakerOn = false;
@@ -99,7 +99,7 @@
         // We do two things:
         // (1) If this is the first call, then we can to turn on bluetooth if available.
         // (2) Unmute the audio for the new incoming call.
-        boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1;
+        boolean isOnlyCall = TelecomSystem.getInstance().getCallsManager().getCalls().size() == 1;
         if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
             mBluetoothManager.connectBluetoothAudio();
             route = AudioState.ROUTE_BLUETOOTH;
@@ -175,7 +175,7 @@
         Log.v(this, "mute, shouldMute: %b", shouldMute);
 
         // Don't mute if there are any emergency calls.
-        if (CallsManager.getInstance().hasEmergencyCall()) {
+        if (TelecomSystem.getInstance().getCallsManager().hasEmergencyCall()) {
             shouldMute = false;
             Log.v(this, "ignoring mute for emergency call");
         }
@@ -325,7 +325,8 @@
         }
 
         if (!oldAudioState.equals(mAudioState)) {
-            CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
+            TelecomSystem.getInstance().getCallsManager()
+                    .onAudioStateChanged(oldAudioState, mAudioState);
             updateAudioForForegroundCall();
         }
     }
@@ -359,8 +360,8 @@
             requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
         } else {
             Call foregroundCall = getForegroundCall();
-            Call waitingForAccountSelectionCall =
-                    CallsManager.getInstance().getFirstCallWithState(CallState.PRE_DIAL_WAIT);
+            Call waitingForAccountSelectionCall = TelecomSystem.getInstance().getCallsManager()
+                    .getFirstCallWithState(CallState.PRE_DIAL_WAIT);
             if (foregroundCall != null && waitingForAccountSelectionCall == null) {
                 // In the case where there is a call that is waiting for account selection,
                 // this will fall back to abandonAudioFocus() below, which temporarily exits
@@ -503,7 +504,7 @@
     }
 
     private void updateAudioForForegroundCall() {
-        Call call = CallsManager.getInstance().getForegroundCall();
+        Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
         if (call != null && call.getConnectionService() != null) {
             call.getConnectionService().onAudioStateChanged(call, mAudioState);
         }
@@ -513,7 +514,7 @@
      * Returns the current foreground call in order to properly set the audio mode.
      */
     private Call getForegroundCall() {
-        Call call = CallsManager.getInstance().getForegroundCall();
+        Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
 
         // We ignore any foreground call that is in the ringing state because we deal with ringing
         // calls exclusively through the mIsRinging variable set by {@link Ringer}.
@@ -525,7 +526,7 @@
     }
 
     private boolean hasRingingForegroundCall() {
-        Call call = CallsManager.getInstance().getForegroundCall();
+        Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
         return call != null && call.getState() == CallState.RINGING;
     }
 
diff --git a/src/com/android/server/telecom/CallReceiver.java b/src/com/android/server/telecom/CallIntentProcessor.java
similarity index 71%
rename from src/com/android/server/telecom/CallReceiver.java
rename to src/com/android/server/telecom/CallIntentProcessor.java
index 27d4f51..3c3b98f 100644
--- a/src/com/android/server/telecom/CallReceiver.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -1,6 +1,7 @@
 package com.android.server.telecom;
 
-import android.content.BroadcastReceiver;
+import com.android.server.telecom.components.ErrorDialogActivity;
+
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
@@ -16,40 +17,49 @@
 import android.widget.Toast;
 
 /**
- * Single point of entry for all outgoing and incoming calls. {@link CallActivity} serves as a
- * trampoline activity that captures call intents for individual users and forwards it to
- * the {@link CallReceiver} which interacts with the rest of Telecom, both of which run only as
+ * 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.
  */
-public class CallReceiver extends BroadcastReceiver {
-    private static final String TAG = CallReceiver.class.getName();
+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";
+    static final String KEY_IS_DEFAULT_DIALER = "is_default_dialer";
 
-    @Override
-    public void onReceive(Context context, Intent intent) {
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+
+    public CallIntentProcessor(Context context, CallsManager callsManager) {
+        this.mContext = context;
+        this.mCallsManager = callsManager;
+    }
+
+    public void processIntent(Intent intent) {
         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
 
         Trace.beginSection("processNewCallCallIntent");
         if (isUnknownCall) {
-            processUnknownCallIntent(intent);
+            processUnknownCallIntent(mCallsManager, intent);
         } else {
-            processOutgoingCallIntent(context, intent);
+            processOutgoingCallIntent(mContext, mCallsManager, intent);
         }
         Trace.endSection();
     }
 
+
     /**
      * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
      *
      * @param intent Call intent containing data about the handle to call.
      */
-    static void processOutgoingCallIntent(Context context, Intent intent) {
-        if (shouldPreventDuplicateVideoCall(context, intent)) {
+    static void processOutgoingCallIntent(
+            Context context,
+            CallsManager callsManager,
+            Intent intent) {
+        if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) {
             return;
         }
 
@@ -76,7 +86,7 @@
         final boolean isDefaultDialer = intent.getBooleanExtra(KEY_IS_DEFAULT_DIALER, false);
 
         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
-        Call call = getCallsManager().startOutgoingCall(handle, phoneAccountHandle, clientExtras);
+        Call call = callsManager.startOutgoingCall(handle, phoneAccountHandle, clientExtras);
 
         if (call != null) {
             // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
@@ -85,7 +95,7 @@
             // process will be running throughout the duration of the phone call and should never
             // be killed.
             NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
-                    context, getCallsManager(), call, intent, isDefaultDialer);
+                    context, callsManager, call, intent, isDefaultDialer);
             final int result = broadcaster.processIntent();
             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
 
@@ -95,16 +105,18 @@
         }
     }
 
-    static void processIncomingCallIntent(Intent intent) {
+    static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
 
         if (phoneAccountHandle == null) {
-            Log.w(TAG, "Rejecting incoming call due to null phone account");
+            Log.w(CallIntentProcessor.class,
+                    "Rejecting incoming call due to null phone account");
             return;
         }
         if (phoneAccountHandle.getComponentName() == null) {
-            Log.w(TAG, "Rejecting incoming call due to null component name");
+            Log.w(CallIntentProcessor.class,
+                    "Rejecting incoming call due to null component name");
             return;
         }
 
@@ -116,29 +128,26 @@
             clientExtras = Bundle.EMPTY;
         }
 
-        Log.d(TAG, "Processing incoming call from connection service [%s]",
+        Log.d(CallIntentProcessor.class,
+                "Processing incoming call from connection service [%s]",
                 phoneAccountHandle.getComponentName());
-        getCallsManager().processIncomingCallIntent(phoneAccountHandle, clientExtras);
+        callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras);
     }
 
-    private void processUnknownCallIntent(Intent intent) {
+    private static void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
 
         if (phoneAccountHandle == null) {
-            Log.w(this, "Rejecting unknown call due to null phone account");
+            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account");
             return;
         }
         if (phoneAccountHandle.getComponentName() == null) {
-            Log.w(this, "Rejecting unknown call due to null component name");
+            Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name");
             return;
         }
 
-        getCallsManager().addNewUnknownCall(phoneAccountHandle, intent.getExtras());
-    }
-
-    static CallsManager getCallsManager() {
-        return CallsManager.getInstance();
+        callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
     }
 
     private static void disconnectCallAndShowErrorDialog(
@@ -167,11 +176,14 @@
      * @return {@code true} if the outgoing call is a video call and should be prevented from going
      *     out, {@code false} otherwise.
      */
-    private static boolean shouldPreventDuplicateVideoCall(Context context, Intent intent) {
+    private static boolean shouldPreventDuplicateVideoCall(
+            Context context,
+            CallsManager callsManager,
+            Intent intent) {
         int intentVideoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 VideoProfile.VideoState.AUDIO_ONLY);
         if (intentVideoState == VideoProfile.VideoState.AUDIO_ONLY
-                || !getCallsManager().hasVideoCall()) {
+                || !callsManager.hasVideoCall()) {
             return false;
         } else {
             // Display an error toast to the user.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f412ab1..2c6109f 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -77,11 +77,6 @@
         void onCanAddCallChanged(boolean canAddCall);
     }
 
-    /**
-     * Singleton instance of the {@link CallsManager}, initialized from {@link TelecomService}.
-     */
-    private static CallsManager sInstance = null;
-
     private static final String TAG = "CallsManager";
 
     private static final int MAXIMUM_LIVE_CALLS = 1;
@@ -141,25 +136,12 @@
 
     private Runnable mStopTone;
 
-    /** Singleton accessor. */
-    static CallsManager getInstance() {
-        return sInstance;
-    }
-
-    /**
-     * Sets the static singleton instance.
-     *
-     * @param instance The instance to set.
-     */
-    static void initialize(CallsManager instance) {
-        sInstance = instance;
-    }
-
     /**
      * Initializes the required Telecom components.
      */
      CallsManager(Context context, MissedCallNotifier missedCallNotifier,
-             PhoneAccountRegistrar phoneAccountRegistrar) {
+             PhoneAccountRegistrar phoneAccountRegistrar,
+             RespondViaSmsManager respondViaSmsManager) {
         mContext = context;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
@@ -190,7 +172,7 @@
         mListeners.add(missedCallNotifier);
         mListeners.add(mDtmfLocalTonePlayer);
         mListeners.add(mHeadsetMediaButton);
-        mListeners.add(RespondViaSmsManager.getInstance());
+        mListeners.add(respondViaSmsManager);
         mListeners.add(mProximitySensorManager);
     }
 
@@ -416,6 +398,7 @@
         Uri handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         Call call = new Call(
                 mContext,
+                this,
                 mConnectionServiceRepository,
                 handle,
                 null /* gatewayInfo */,
@@ -435,6 +418,7 @@
         Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
         Call call = new Call(
                 mContext,
+                this,
                 mConnectionServiceRepository,
                 handle,
                 null /* gatewayInfo */,
@@ -471,6 +455,7 @@
         // to a connection service, but in most cases will remain the same.
         return new Call(
                 mContext,
+                this,
                 mConnectionServiceRepository,
                 handle,
                 null /* gatewayInfo */,
@@ -872,7 +857,7 @@
      * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
      * last live call, then also disconnect from the in-call controller.
      *
-     * @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}.
+     * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
     void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
         call.setDisconnectCause(disconnectCause);
@@ -1052,6 +1037,7 @@
 
         Call call = new Call(
                 mContext,
+                this,
                 mConnectionServiceRepository,
                 null /* handle */,
                 null /* gatewayInfo */,
@@ -1398,6 +1384,7 @@
     Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
         Call call = new Call(
                 mContext,
+                this,
                 mConnectionServiceRepository,
                 connection.getHandle() /* handle */,
                 null /* gatewayInfo */,
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index 0d73371..5490c2d 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -28,13 +28,20 @@
 /**
  * Searches for and returns connection services.
  */
-final class ConnectionServiceRepository
-        implements ServiceBinder.Listener<ConnectionServiceWrapper> {
+final class ConnectionServiceRepository {
     private final HashMap<Pair<ComponentName, UserHandle>, ConnectionServiceWrapper> mServiceCache =
             new HashMap<>();
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final Context mContext;
 
+    private final ServiceBinder.Listener<ConnectionServiceWrapper> mUnbindListener =
+            new ServiceBinder.Listener<ConnectionServiceWrapper>() {
+                @Override
+                public void onUnbind(ConnectionServiceWrapper service) {
+                    mServiceCache.remove(service.getComponentName());
+                }
+            };
+
     ConnectionServiceRepository(PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mContext = context;
@@ -50,23 +57,13 @@
                     mPhoneAccountRegistrar,
                     mContext,
                     userHandle);
-            service.addListener(this);
+            service.addListener(mUnbindListener);
             mServiceCache.put(cacheKey, service);
         }
         return service;
     }
 
     /**
-     * Removes the specified service from the cache when the service unbinds.
-     *
-     * {@inheritDoc}
-     */
-    @Override
-    public void onUnbind(ConnectionServiceWrapper service) {
-        mServiceCache.remove(service.getComponentName());
-    }
-
-    /**
      * Dumps the state of the {@link ConnectionServiceRepository}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index d470737..2f125cf 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -60,7 +60,7 @@
  * {@link IConnectionService} directly and instead should use this class to invoke methods of
  * {@link IConnectionService}.
  */
-final class ConnectionServiceWrapper extends ServiceBinder<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;
@@ -104,7 +104,7 @@
                 case MSG_SET_ACTIVE:
                     call = mCallIdMapper.getCall(msg.obj);
                     if (call != null) {
-                        mCallsManager.markCallAsActive(call);
+                        TelecomSystem.getInstance().getCallsManager().markCallAsActive(call);
                     } else {
                         //Log.w(this, "setActive, unknown call id: %s", msg.obj);
                     }
@@ -112,7 +112,7 @@
                 case MSG_SET_RINGING:
                     call = mCallIdMapper.getCall(msg.obj);
                     if (call != null) {
-                        mCallsManager.markCallAsRinging(call);
+                        TelecomSystem.getInstance().getCallsManager().markCallAsRinging(call);
                     } else {
                         //Log.w(this, "setRinging, unknown call id: %s", msg.obj);
                     }
@@ -120,7 +120,7 @@
                 case MSG_SET_DIALING:
                     call = mCallIdMapper.getCall(msg.obj);
                     if (call != null) {
-                        mCallsManager.markCallAsDialing(call);
+                        TelecomSystem.getInstance().getCallsManager().markCallAsDialing(call);
                     } else {
                         //Log.w(this, "setDialing, unknown call id: %s", msg.obj);
                     }
@@ -132,7 +132,8 @@
                         DisconnectCause disconnectCause = (DisconnectCause) args.arg2;
                         Log.d(this, "disconnect call %s %s", disconnectCause, call);
                         if (call != null) {
-                            mCallsManager.markCallAsDisconnected(call, disconnectCause);
+                            TelecomSystem.getInstance().getCallsManager()
+                                    .markCallAsDisconnected(call, disconnectCause);
                         } else {
                             //Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
                         }
@@ -144,7 +145,7 @@
                 case MSG_SET_ON_HOLD:
                     call = mCallIdMapper.getCall(msg.obj);
                     if (call != null) {
-                        mCallsManager.markCallAsOnHold(call);
+                        TelecomSystem.getInstance().getCallsManager().markCallAsOnHold(call);
                     } else {
                         //Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
                     }
@@ -224,7 +225,7 @@
                                 parcelableConference.getPhoneAccount() != null) {
                             phAcc = parcelableConference.getPhoneAccount();
                         }
-                        Call conferenceCall = mCallsManager.createConferenceCall(
+                        Call conferenceCall = TelecomSystem.getInstance().getCallsManager().createConferenceCall(
                                 phAcc, parcelableConference);
                         mCallIdMapper.addCall(conferenceCall, id);
                         conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
@@ -247,10 +248,10 @@
                     call = mCallIdMapper.getCall(msg.obj);
                     if (call != null) {
                         if (call.isAlive()) {
-                            mCallsManager.markCallAsDisconnected(
+                            TelecomSystem.getInstance().getCallsManager().markCallAsDisconnected(
                                     call, new DisconnectCause(DisconnectCause.REMOTE));
                         } else {
-                            mCallsManager.markCallAsRemoved(call);
+                            TelecomSystem.getInstance().getCallsManager().markCallAsRemoved(call);
                         }
                     }
                     break;
@@ -380,7 +381,8 @@
                     try {
                         String callId = (String)args.arg1;
                         ParcelableConnection connection = (ParcelableConnection)args.arg2;
-                        Call existingCall = mCallsManager.createCallForExistingConnection(callId,
+                        Call existingCall = TelecomSystem.getInstance().getCallsManager()
+                                .createCallForExistingConnection(callId,
                                 connection);
                         mCallIdMapper.addCall(existingCall, callId);
                         existingCall.setConnectionService(ConnectionServiceWrapper.this);
@@ -486,7 +488,8 @@
         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)
+                mHandler.obtainMessage(
+                        MSG_SET_CONNECTION_CAPABILITIES, connectionCapabilities, 0, callId)
                         .sendToTarget();
             } else {
                 Log.w(this, "ID not valid for setCallCapabilities");
@@ -617,14 +620,6 @@
     }
 
     private final Adapter mAdapter = new Adapter();
-    private final CallsManager mCallsManager = CallsManager.getInstance();
-    /**
-     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
-     * load factor before resizing, 1 means we only expect a single thread to
-     * access the map so make only a single shard
-     */
-    private final Set<Call> mPendingConferenceCalls = Collections.newSetFromMap(
-            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
     private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
     private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
 
@@ -938,7 +933,7 @@
             // outgoing calls to try the next service. This needs to happen before CallsManager
             // tries to clean up any calls still associated with this service.
             handleConnectionServiceDeath();
-            CallsManager.getInstance().handleConnectionServiceDeath(this);
+            TelecomSystem.getInstance().getCallsManager().handleConnectionServiceDeath(this);
             mServiceInterface = null;
         } else {
             mServiceInterface = IConnectionService.Stub.asInterface(binder);
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 1fbf5ee..69010d2 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -244,6 +244,4 @@
         // notify the thread to begin working
         sThreadHandler.sendMessage(msg);
     }
-
-
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index a5da3a3..2df6ffb 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -175,7 +175,7 @@
     @Override
     public void onCallRemoved(Call call) {
         Log.i(this, "onCallRemoved: %s", call);
-        if (CallsManager.getInstance().getCalls().isEmpty()) {
+        if (TelecomSystem.getInstance().getCallsManager().getCalls().isEmpty()) {
             // TODO: Wait for all messages to be delivered to the service before unbinding.
             unbind();
         }
@@ -355,8 +355,10 @@
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
 
         try {
-            inCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
-                    mCallIdMapper));
+            inCallService.setInCallAdapter(
+                    new InCallAdapter(
+                            TelecomSystem.getInstance().getCallsManager(),
+                            mCallIdMapper));
             mInCallServices.put(componentName, inCallService);
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
@@ -365,7 +367,7 @@
         }
 
         // Upon successful connection, send the state of the world to the service.
-        Collection<Call> calls = CallsManager.getInstance().getCalls();
+        Collection<Call> calls = TelecomSystem.getInstance().getCallsManager().getCalls();
         if (!calls.isEmpty()) {
             Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
                     componentName);
@@ -380,8 +382,10 @@
                 } catch (RemoteException ignored) {
                 }
             }
-            onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
-            onCanAddCallChanged(CallsManager.getInstance().canAddCall());
+            onAudioStateChanged(
+                    null,
+                    TelecomSystem.getInstance().getCallsManager().getAudioState());
+            onCanAddCallChanged(TelecomSystem.getInstance().getCallsManager().canAddCall());
         } else {
             unbind();
         }
@@ -407,7 +411,7 @@
             // implementations.
             if (disconnectedComponent.equals(mInCallComponentName)) {
                 Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
-                CallsManager.getInstance().disconnectAllCalls();
+                TelecomSystem.getInstance().getCallsManager().disconnectAllCalls();
                 unbind();
             } else {
                 Log.i(this, "In-Call Service %s suddenly disconnected", disconnectedComponent);
@@ -464,8 +468,8 @@
 
         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
         boolean isDefaultSmsAccount =
-                CallsManager.getInstance().getPhoneAccountRegistrar().isUserSelectedSmsPhoneAccount(
-                        call.getTargetPhoneAccount());
+                TelecomSystem.getInstance().getCallsManager().getPhoneAccountRegistrar()
+                        .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
         if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
             capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
         }
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 2c1e78b..dbbc49e 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -68,6 +68,7 @@
     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.
@@ -81,6 +82,10 @@
         updateOnStartup();
     }
 
+    void setCallsManager(CallsManager callsManager) {
+        this.mCallsManager = callsManager;
+    }
+
     /** {@inheritDoc} */
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
@@ -233,7 +238,7 @@
      */
     private PendingIntent createClearMissedCallsPendingIntent() {
         return createTelecomPendingIntent(
-                TelecomBroadcastReceiver.ACTION_CLEAR_MISSED_CALLS, null);
+                TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null);
     }
 
     /**
@@ -244,7 +249,7 @@
      */
     private PendingIntent createCallBackPendingIntent(Uri handle) {
         return createTelecomPendingIntent(
-                TelecomBroadcastReceiver.ACTION_CALL_BACK_FROM_NOTIFICATION, handle);
+                TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle);
     }
 
     /**
@@ -253,19 +258,19 @@
      */
     private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle) {
         return createTelecomPendingIntent(
-                TelecomBroadcastReceiver.ACTION_SEND_SMS_FROM_NOTIFICATION,
+                TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
                 Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null));
     }
 
     /**
      * Creates generic pending intent from the specified parameters to be received by
-     * {@link TelecomBroadcastReceiver}.
+     * {@link TelecomBroadcastIntentProcessor}.
      *
      * @param action The intent action.
      * @param data The intent data.
      */
     private PendingIntent createTelecomPendingIntent(String action, Uri data) {
-        Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
+        Intent intent = new Intent(action, data, mContext, TelecomBroadcastIntentProcessor.class);
         return PendingIntent.getBroadcast(mContext, 0, intent, 0);
     }
 
@@ -308,7 +313,8 @@
                             }
 
                             // Convert the data to a call object
-                            Call call = new Call(mContext, null, null, null, null, null, true,
+                            Call call = new Call(mContext, mCallsManager,
+                                    null, null, null, null, null, true,
                                     false);
                             call.setDisconnectCause(new DisconnectCause(DisconnectCause.MISSED));
                             call.setState(CallState.DISCONNECTED);
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index c52f2bb..eb43991 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -156,7 +156,7 @@
      * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
      * - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
      *
-     * @return {@link CallActivity#OUTGOING_CALL_SUCCEEDED} if the call succeeded, and an
+     * @return {@link UserCallIntentProcessor#OUTGOING_CALL_SUCCEEDED} if the call succeeded, and an
      *         appropriate {@link DisconnectCause} if the call did not, describing why it failed.
      */
     int processIntent() {
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index b96eb02..1a1f427 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -43,7 +43,8 @@
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
         if ((newState == CallState.DIALING || newState == CallState.ACTIVE
-                || newState == CallState.ON_HOLD) && !CallsManager.getInstance().hasRingingCall()) {
+                || newState == CallState.ON_HOLD) &&
+                !TelecomSystem.getInstance().getCallsManager().hasRingingCall()) {
             /*
              * EXTRA_STATE_RINGING takes precedence over EXTRA_STATE_OFFHOOK, so if there is
              * already a ringing call, don't broadcast EXTRA_STATE_OFFHOOK.
@@ -63,7 +64,7 @@
     public void onCallRemoved(Call call) {
         // Recalculate the current phone state based on the consolidated state of the remaining
         // calls in the call list.
-        final CallsManager callsManager = CallsManager.getInstance();
+        final CallsManager callsManager = TelecomSystem.getInstance().getCallsManager();
         int callState = TelephonyManager.CALL_STATE_IDLE;
         if (callsManager.hasRingingCall()) {
             callState = TelephonyManager.CALL_STATE_RINGING;
diff --git a/src/com/android/server/telecom/ProximitySensorManager.java b/src/com/android/server/telecom/ProximitySensorManager.java
index 5b82c43..d18e4ac 100644
--- a/src/com/android/server/telecom/ProximitySensorManager.java
+++ b/src/com/android/server/telecom/ProximitySensorManager.java
@@ -41,7 +41,7 @@
 
     @Override
     public void onCallRemoved(Call call) {
-        if (CallsManager.getInstance().getCalls().isEmpty()) {
+        if (TelecomSystem.getInstance().getCallsManager().getCalls().isEmpty()) {
             Log.i(this, "All calls removed, resetting proximity sensor to default state");
             turnOff(true);
         }
@@ -52,7 +52,7 @@
      * Turn the proximity sensor on.
      */
     void turnOn() {
-        if (CallsManager.getInstance().getCalls().isEmpty()) {
+        if (TelecomSystem.getInstance().getCallsManager().getCalls().isEmpty()) {
             Log.w(this, "Asking to turn on prox sensor without a call? I don't think so.");
             return;
         }
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index ebedf9f..3a3432e 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -44,8 +44,6 @@
     private static final int MSG_CANNED_TEXT_MESSAGES_READY = 1;
     private static final int MSG_SHOW_SENT_TOAST = 2;
 
-    private static final RespondViaSmsManager sInstance = new RespondViaSmsManager();
-
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -82,9 +80,7 @@
         }
     };
 
-    public static RespondViaSmsManager getInstance() { return sInstance; }
-
-    private RespondViaSmsManager() {}
+    public RespondViaSmsManager() {}
 
     /**
      * Read the (customizable) canned responses from SharedPreferences,
@@ -144,7 +140,7 @@
     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
         if (rejectWithMessage && call.getHandle() != null) {
             PhoneAccountRegistrar phoneAccountRegistrar =
-                    CallsManager.getInstance().getPhoneAccountRegistrar();
+                    TelecomSystem.getInstance().getCallsManager().getPhoneAccountRegistrar();
             int subId = phoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
                     call.getTargetPhoneAccount());
             rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(),
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 9a5ad03..8baf3ec 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -38,7 +38,7 @@
  * Subclasses supply the service intent and component name and this class will invoke protected
  * methods when the class is bound, unbound, or upon failure.
  */
-abstract class ServiceBinder<ServiceInterface extends IInterface> {
+abstract class ServiceBinder {
 
     /**
      * Callback to notify after a binding succeeds or fails.
@@ -51,7 +51,7 @@
     /**
      * Listener for bind events on ServiceBinder.
      */
-    interface Listener<ServiceBinderClass extends ServiceBinder<?>> {
+    interface Listener<ServiceBinderClass extends ServiceBinder> {
         void onUnbind(ServiceBinderClass serviceBinder);
     }
 
diff --git a/src/com/android/server/telecom/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
similarity index 84%
rename from src/com/android/server/telecom/TelecomBroadcastReceiver.java
rename to src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index cc5116d..3110d46 100644
--- a/src/com/android/server/telecom/TelecomBroadcastReceiver.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
@@ -25,7 +24,7 @@
  * Handles miscellaneous Telecom broadcast intents. This should be visible from outside, but
  * should not be in the "exported" state.
  */
-public final class TelecomBroadcastReceiver extends BroadcastReceiver {
+public final class TelecomBroadcastIntentProcessor {
     /** The action used to send SMS response for the missed call notification. */
     static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
             "com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION";
@@ -38,35 +37,40 @@
     static final String ACTION_CLEAR_MISSED_CALLS =
             "com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
 
-    /** {@inheritDoc} */
-    @Override
-    public void onReceive(Context context, Intent intent) {
+    private final Context mContext;
+
+    public TelecomBroadcastIntentProcessor(Context context) {
+        mContext = context;
+    }
+
+    public void processIntent(Intent intent) {
         String action = intent.getAction();
 
         Log.v(this, "Action received: %s.", action);
 
-        MissedCallNotifier missedCallNotifier = CallsManager.getInstance().getMissedCallNotifier();
+        MissedCallNotifier missedCallNotifier = TelecomSystem.getInstance()
+                .getCallsManager().getMissedCallNotifier();
 
         // Send an SMS from the missed call notification.
         if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
             // Close the notification shade and the notification itself.
-            closeSystemDialogs(context);
+            closeSystemDialogs(mContext);
             missedCallNotifier.clearMissedCalls();
 
             Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
             callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            context.startActivity(callIntent);
+            mContext.startActivity(callIntent);
 
         // Call back recent caller from the missed call notification.
         } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
             // Close the notification shade and the notification itself.
-            closeSystemDialogs(context);
+            closeSystemDialogs(mContext);
             missedCallNotifier.clearMissedCalls();
 
             Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
             callIntent.setFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            context.startActivity(callIntent);
+            mContext.startActivity(callIntent);
 
         // Clear the missed call notification and call log entries.
         } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
diff --git a/src/com/android/server/telecom/TelecomGlobals.java b/src/com/android/server/telecom/TelecomGlobals.java
deleted file mode 100644
index cf0936c..0000000
--- a/src/com/android/server/telecom/TelecomGlobals.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom;
-
-import android.app.Application;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-/**
- * Top-level Application class for Telecom.
- */
-public final class TelecomGlobals {
-    private static final String TAG = TelecomGlobals.class.getSimpleName();
-
-    private static final IntentFilter USER_SWITCHED_FILTER =
-            new IntentFilter(Intent.ACTION_USER_SWITCHED);
-
-    private static final TelecomGlobals INSTANCE = new TelecomGlobals();
-
-    /**
-     * The Telecom service implementation.
-     */
-    private TelecomService mTelecomService;
-
-    /**
-     * Missed call notifier. Exists here so that the instance can be shared with
-     * {@link TelecomBroadcastReceiver}.
-     */
-    private MissedCallNotifier mMissedCallNotifier;
-
-    /**
-     * Maintains the list of registered {@link android.telecom.PhoneAccountHandle}s.
-     */
-    private PhoneAccountRegistrar mPhoneAccountRegistrar;
-
-    /**
-     * The calls manager for the Telecom service.
-     */
-    private CallsManager mCallsManager;
-
-    /**
-     * The application context.
-     */
-    private Context mContext;
-
-    private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-            UserHandle currentUserHandle = new UserHandle(userHandleId);
-            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
-        }
-    };
-
-    static TelecomGlobals getInstance() {
-        return INSTANCE;
-    }
-
-    void initialize(Context context) {
-        if (mContext != null) {
-            Log.e(TAG, new Exception(), "Attempting to intialize TelecomGlobals a second time.");
-            return;
-        } else {
-            Log.i(TAG, "TelecomGlobals initializing");
-        }
-        mContext = context.getApplicationContext();
-
-        mMissedCallNotifier = new MissedCallNotifier(mContext);
-        mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
-
-        mCallsManager = new CallsManager(mContext, mMissedCallNotifier, mPhoneAccountRegistrar);
-        CallsManager.initialize(mCallsManager);
-        Log.i(this, "CallsManager initialized");
-
-        // Start the BluetoothPhoneService
-        BluetoothPhoneService.start(mContext);
-
-        mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
-    }
-
-    MissedCallNotifier getMissedCallNotifier() {
-        return mMissedCallNotifier;
-    }
-
-    PhoneAccountRegistrar getPhoneAccountRegistrar() {
-        return mPhoneAccountRegistrar;
-    }
-
-    CallsManager getCallsManager() {
-        return mCallsManager;
-    }
-}
diff --git a/src/com/android/server/telecom/TelecomService.java b/src/com/android/server/telecom/TelecomServiceImpl.java
similarity index 86%
rename from src/com/android/server/telecom/TelecomService.java
rename to src/com/android/server/telecom/TelecomServiceImpl.java
index 0e0f0a1..e9df1a7 100644
--- a/src/com/android/server/telecom/TelecomService.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -17,9 +17,7 @@
 package com.android.server.telecom;
 
 import android.Manifest;
-import android.annotation.SdkConstant;
 import android.app.AppOpsManager;
-import android.app.Service;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -55,13 +53,7 @@
 /**
  * Implementation of the ITelecom interface.
  */
-public class TelecomService extends Service {
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
-    public static final String SERVICE_INTERFACE = "android.telecom.ITelecomService";
-
+public class TelecomServiceImpl {
     /** The context. */
     private Context mContext;
 
@@ -89,10 +81,10 @@
                 Object result = null;
                 switch (msg.what) {
                     case MSG_SILENCE_RINGER:
-                        mCallsManager.getRinger().silence();
+                        TelecomSystem.getInstance().getCallsManager().getRinger().silence();
                         break;
                     case MSG_SHOW_CALL_SCREEN:
-                        mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
+                        TelecomSystem.getInstance().getCallsManager().getInCallController().bringToForeground(msg.arg1 == 1);
                         break;
                     case MSG_END_CALL:
                         result = endCallInternal();
@@ -101,20 +93,22 @@
                         acceptRingingCallInternal();
                         break;
                     case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
-                        mMissedCallNotifier.clearMissedCalls();
+                        TelecomSystem.getInstance().getMissedCallNotifier().clearMissedCalls();
                         break;
                     case MSG_IS_TTY_SUPPORTED:
-                        result = mCallsManager.isTtySupported();
+                        result = TelecomSystem.getInstance().getCallsManager().isTtySupported();
                         break;
                     case MSG_GET_CURRENT_TTY_MODE:
-                        result = mCallsManager.getCurrentTtyMode();
+                        result = TelecomSystem.getInstance().getCallsManager().getCurrentTtyMode();
                         break;
                     case MSG_NEW_INCOMING_CALL:
                         if (request.arg == null || !(request.arg instanceof Intent)) {
                             Log.w(this, "Invalid new incoming call request");
                             break;
                         }
-                        CallReceiver.processIncomingCallIntent((Intent) request.arg);
+                        CallIntentProcessor.processIncomingCallIntent(
+                                mCallsManager,
+                                (Intent) request.arg);
                         break;
                 }
 
@@ -128,7 +122,7 @@
         }
     }
 
-    private static final String TAG = TelecomService.class.getSimpleName();
+    private static final String TAG = com.android.server.telecom.TelecomServiceImpl.class.getSimpleName();
 
     private static final String SERVICE_NAME = "telecom";
 
@@ -143,51 +137,39 @@
 
     private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
 
-    private CallsManager mCallsManager;
-    private MissedCallNotifier mMissedCallNotifier;
-    private PhoneAccountRegistrar mPhoneAccountRegistrar;
     private AppOpsManager mAppOpsManager;
     private UserManager mUserManager;
     private PackageManager mPackageManager;
-    private TelecomServiceImpl mServiceImpl;
+    private TelecomBinderImpl mBinderImpl;
+    private CallsManager mCallsManager;
 
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        Log.d(this, "onCreate");
-        mContext = this;
+    public TelecomServiceImpl(Context context, CallsManager callsManager) {
+        mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mServiceImpl = new TelecomServiceImpl();
+        mBinderImpl = new TelecomBinderImpl();
 
-        TelecomGlobals globals = TelecomGlobals.getInstance();
-        globals.initialize(this);
-
-        mMissedCallNotifier = globals.getMissedCallNotifier();
-        mPhoneAccountRegistrar = globals.getPhoneAccountRegistrar();
-        mCallsManager = globals.getCallsManager();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mPackageManager = mContext.getPackageManager();
+
+        mCallsManager = callsManager;
     }
 
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.d(this, "onBind");
-        return mServiceImpl;
+    public IBinder getBinder() {
+        return mBinderImpl;
     }
 
     /**
      * Implementation of the ITelecomService interface.
      * TODO: Reorganize this inner class to top of file.
      */
-    class TelecomServiceImpl extends ITelecomService.Stub {
+    class TelecomBinderImpl extends ITelecomService.Stub {
         @Override
         public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
             enforceReadPermission();
             long token = Binder.clearCallingIdentity();
             try {
                 PhoneAccountHandle defaultOutgoingPhoneAccount =
-                        mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount(uriScheme);
                 // Make sure that the calling user can see this phone account.
                 if (defaultOutgoingPhoneAccount != null
                         && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
@@ -207,7 +189,7 @@
         public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
             try {
                 PhoneAccountHandle userSelectedOutgoingPhoneAccount =
-                        mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+                        TelecomSystem.getInstance().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");
@@ -225,7 +207,7 @@
             enforceModifyPermission();
 
             try {
-                mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(accountHandle);
             } catch (Exception e) {
                 Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
                 throw e;
@@ -238,7 +220,7 @@
             long token = Binder.clearCallingIdentity();
             try {
                 return filterForAccountsVisibleToCaller(
-                        mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getCallCapablePhoneAccounts());
             } catch (Exception e) {
                 Log.e(this, e, "getCallCapablePhoneAccounts");
                 throw e;
@@ -253,7 +235,7 @@
             long token = Binder.clearCallingIdentity();
             try {
                 return filterForAccountsVisibleToCaller(
-                        mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getCallCapablePhoneAccounts(uriScheme));
             } catch (Exception e) {
                 Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
                 throw e;
@@ -266,7 +248,7 @@
         public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
             try {
                 return filterForAccountsVisibleToCaller(
-                        mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getPhoneAccountsForPackage(packageName));
             } catch (Exception e) {
                 Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
                 throw e;
@@ -280,7 +262,7 @@
                     Log.w(this, "%s is not visible for the calling user", accountHandle);
                     return null;
                 }
-                return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
+                return TelecomSystem.getInstance().getPhoneAccountRegistrar().getPhoneAccountInternal(accountHandle);
             } catch (Exception e) {
                 Log.e(this, e, "getPhoneAccount %s", accountHandle);
                 throw e;
@@ -301,7 +283,7 @@
         @Override
         public List<PhoneAccount> getAllPhoneAccounts() {
             try {
-                List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
+                List<PhoneAccount> allPhoneAccounts = TelecomSystem.getInstance().getPhoneAccountRegistrar().getAllPhoneAccounts();
                 List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
                 for (PhoneAccount phoneAccount : profilePhoneAccounts) {
                     if (isVisibleToCaller(phoneAccount)) {
@@ -319,7 +301,7 @@
         public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
             try {
                 return filterForAccountsVisibleToCaller(
-                        mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getAllPhoneAccountHandles());
             } catch (Exception e) {
                 Log.e(this, e, "getAllPhoneAccounts");
                 throw e;
@@ -329,7 +311,7 @@
         @Override
         public PhoneAccountHandle getSimCallManager() {
             try {
-                PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+                PhoneAccountHandle accountHandle = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSimCallManager();
                 if (!isVisibleToCaller(accountHandle)) {
                     Log.w(this, "%s is not visible for the calling user", accountHandle);
                     return null;
@@ -346,7 +328,7 @@
             enforceModifyPermission();
 
             try {
-                mPhoneAccountRegistrar.setSimCallManager(accountHandle);
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().setSimCallManager(accountHandle);
             } catch (Exception e) {
                 Log.e(this, e, "setSimCallManager");
                 throw e;
@@ -359,7 +341,7 @@
             long token = Binder.clearCallingIdentity();
             try {
                 return filterForAccountsVisibleToCaller(
-                        mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
+                        TelecomSystem.getInstance().getPhoneAccountRegistrar().getConnectionManagerPhoneAccounts());
             } catch (Exception e) {
                 Log.e(this, e, "getSimCallManagers");
                 throw e;
@@ -387,7 +369,7 @@
                 }
                 enforceUserHandleMatchesCaller(account.getAccountHandle());
 
-                mPhoneAccountRegistrar.registerPhoneAccount(account);
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().registerPhoneAccount(account);
             } catch (Exception e) {
                 Log.e(this, e, "registerPhoneAccount %s", account);
                 throw e;
@@ -400,7 +382,7 @@
                 enforcePhoneAccountModificationForPackage(
                         accountHandle.getComponentName().getPackageName());
                 enforceUserHandleMatchesCaller(accountHandle);
-                mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().unregisterPhoneAccount(accountHandle);
             } catch (Exception e) {
                 Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
                 throw e;
@@ -411,7 +393,7 @@
         public void clearAccounts(String packageName) {
             try {
                 enforcePhoneAccountModificationForPackage(packageName);
-                mPhoneAccountRegistrar.clearAccounts(packageName, Binder.getCallingUserHandle());
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().clearAccounts(packageName, Binder.getCallingUserHandle());
             } catch (Exception e) {
                 Log.e(this, e, "clearAccounts %s", packageName);
                 throw e;
@@ -429,7 +411,7 @@
                     Log.w(this, "%s is not visible for the calling user", accountHandle);
                     return false;
                 }
-                return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+                return TelecomSystem.getInstance().getPhoneAccountRegistrar().isVoiceMailNumber(accountHandle, number);
             } catch (Exception e) {
                 Log.e(this, e, "getSubscriptionIdForPhoneAccount");
                 throw e;
@@ -448,7 +430,10 @@
                     return false;
                 }
 
-                int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+                int subId = SubscriptionManager.getDefaultVoiceSubId();
+                if (accountHandle != null) {
+                    subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
+                }
                 return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
             } catch (Exception e) {
                 Log.e(this, e, "getSubscriptionIdForPhoneAccount");
@@ -467,7 +452,7 @@
                     Log.w(this, "%s is not visible for the calling user", accountHandle);
                     return null;
                 }
-                int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+                int subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
                 return getTelephonyManager().getLine1NumberForSubscriber(subId);
             } catch (Exception e) {
                 Log.e(this, e, "getSubscriptionIdForPhoneAccount");
@@ -504,7 +489,7 @@
             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();
+            final int callState = TelecomSystem.getInstance().getCallsManager().getCallState();
             return callState == TelephonyManager.CALL_STATE_OFFHOOK
                     || callState == TelephonyManager.CALL_STATE_RINGING;
         }
@@ -515,7 +500,7 @@
         @Override
         public boolean isRinging() {
             enforceReadPermission();
-            return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+            return TelecomSystem.getInstance().getCallsManager().getCallState() == TelephonyManager.CALL_STATE_RINGING;
         }
 
         /**
@@ -523,7 +508,7 @@
          */
         @Override
         public int getCallState() {
-            return mCallsManager.getCallState();
+            return TelecomSystem.getInstance().getCallsManager().getCallState();
         }
 
         /**
@@ -598,7 +583,7 @@
             long token = Binder.clearCallingIdentity();
             boolean retval = false;
             try {
-                int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+                int subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
                 retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -623,7 +608,7 @@
             long token = Binder.clearCallingIdentity();
             String retval = "content://icc/adn/";
             try {
-                long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+                long subId = TelecomSystem.getInstance().getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount(accountHandle);
                 retval = retval + "subId/" + subId;
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -665,7 +650,7 @@
 
                 Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
-                intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, true);
+                intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
                 if (extras != null) {
                     intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
                 }
@@ -689,10 +674,10 @@
                 enforceUserHandleMatchesCaller(phoneAccountHandle);
 
                 Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
-                intent.setClass(mContext, CallReceiver.class);
+                intent.setClass(mContext, CallIntentProcessor.class);
                 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtras(extras);
-                intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
+                intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
                 mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
             } else {
@@ -720,15 +705,15 @@
             }
 
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-            if (mCallsManager != null) {
-                pw.println("mCallsManager: ");
+            if (TelecomSystem.getInstance().getCallsManager() != null) {
+                pw.println("CallsManager: ");
                 pw.increaseIndent();
-                mCallsManager.dump(pw);
+                TelecomSystem.getInstance().getCallsManager().dump(pw);
                 pw.decreaseIndent();
 
-                pw.println("mPhoneAccountRegistrar: ");
+                pw.println("PhoneAccountRegistrar: ");
                 pw.increaseIndent();
-                mPhoneAccountRegistrar.dump(pw);
+                TelecomSystem.getInstance().getPhoneAccountRegistrar().dump(pw);
                 pw.decreaseIndent();
             }
         }
@@ -743,7 +728,7 @@
             return false;
         }
 
-        return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle));
+        return isVisibleToCaller(TelecomSystem.getInstance().getPhoneAccountRegistrar().getPhoneAccountInternal(accountHandle));
     }
 
     private boolean isVisibleToCaller(PhoneAccount account) {
@@ -820,7 +805,7 @@
     }
 
     private void acceptRingingCallInternal() {
-        Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
+        Call call = TelecomSystem.getInstance().getCallsManager().getFirstCallWithState(CallState.RINGING);
         if (call != null) {
             call.answer(call.getVideoState());
         }
@@ -829,9 +814,9 @@
     private boolean endCallInternal() {
         // Always operate on the foreground call if one exists, otherwise get the first call in
         // priority order by call-state.
-        Call call = mCallsManager.getForegroundCall();
+        Call call = TelecomSystem.getInstance().getCallsManager().getForegroundCall();
         if (call == null) {
-            call = mCallsManager.getFirstCallWithState(
+            call = TelecomSystem.getInstance().getCallsManager().getFirstCallWithState(
                     CallState.ACTIVE,
                     CallState.DIALING,
                     CallState.RINGING,
@@ -940,7 +925,7 @@
                         Binder.getCallingUid(), defaultDialerComponent.getPackageName());
                 return true;
             } catch (SecurityException e) {
-                Log.e(TAG, e, "Could not get default dialer.");
+                Log.e(this, e, "Could not get default dialer.");
             }
         }
         return false;
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
new file mode 100644
index 0000000..3634b8a
--- /dev/null
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+/**
+ * Top-level Application class for Telecom.
+ */
+public final class TelecomSystem {
+
+    /**
+     * This interface is implemented by system-instantiated components (e.g., Services and
+     * Activity-s) that wish to use the TelecomSystem but would like to be testable. Such a
+     * component should implement the getTelecomSystem() method to return the global singleton,
+     * and use its own method. Tests can subclass the component to return a non-singleton.
+     *
+     * A refactoring goal for Telecom is to limit use of the TelecomSystem singleton to those
+     * system-instantiated components, and have all other parts of the system just take all their
+     * dependencies as explicit arguments to their constructor or other methods.
+     */
+    public interface Component {
+        TelecomSystem getTelecomSystem();
+    }
+
+    private static final IntentFilter USER_SWITCHED_FILTER =
+            new IntentFilter(Intent.ACTION_USER_SWITCHED);
+
+    private static TelecomSystem INSTANCE = null;
+
+    private final MissedCallNotifier mMissedCallNotifier;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final CallsManager mCallsManager;
+    private final RespondViaSmsManager mRespondViaSmsManager;
+    private final Context mContext;
+    private final BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
+    private final CallIntentProcessor mCallIntentProcessor;
+    private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
+    private final TelecomServiceImpl mTelecomServiceImpl;
+
+    private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+            UserHandle currentUserHandle = new UserHandle(userHandleId);
+            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+        }
+    };
+
+    public static TelecomSystem getInstance() {
+        return INSTANCE;
+    }
+
+    public static void setInstance(TelecomSystem instance) {
+        if (INSTANCE != null) {
+            throw new RuntimeException("Attempt to set TelecomSystem.INSTANCE twice");
+        }
+        Log.i(TelecomSystem.class, "TelecomSystem.INSTANCE being set");
+        INSTANCE = instance;
+    }
+
+    public TelecomSystem(Context context) {
+        mContext = context.getApplicationContext();
+
+        mMissedCallNotifier = new MissedCallNotifier(mContext);
+        mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
+
+        mRespondViaSmsManager = new RespondViaSmsManager();
+
+        mCallsManager = new CallsManager(
+                mContext, mMissedCallNotifier, mPhoneAccountRegistrar, mRespondViaSmsManager);
+        Log.i(this, "CallsManager initialized");
+        mMissedCallNotifier.setCallsManager(mCallsManager);
+
+        mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
+        mBluetoothPhoneServiceImpl =
+                new BluetoothPhoneServiceImpl(context, mCallsManager, mPhoneAccountRegistrar);
+        mCallIntentProcessor = new CallIntentProcessor(context, mCallsManager);
+        mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(context);
+        mTelecomServiceImpl = new TelecomServiceImpl(context, mCallsManager);
+    }
+
+    public MissedCallNotifier getMissedCallNotifier() {
+        return mMissedCallNotifier;
+    }
+
+    public PhoneAccountRegistrar getPhoneAccountRegistrar() {
+        return mPhoneAccountRegistrar;
+    }
+
+    public CallsManager getCallsManager() {
+        return mCallsManager;
+    }
+
+    public RespondViaSmsManager getRespondViaSmsManager() {
+        return mRespondViaSmsManager;
+    }
+
+    public BluetoothPhoneServiceImpl getBluetoothPhoneServiceImpl() {
+        return mBluetoothPhoneServiceImpl;
+    }
+
+    public CallIntentProcessor getCallIntentProcessor() {
+        return mCallIntentProcessor;
+    }
+
+    public TelecomBroadcastIntentProcessor getTelecomBroadcastIntentProcessor() {
+        return mTelecomBroadcastIntentProcessor;
+    }
+
+    public TelecomServiceImpl getTelecomServiceImpl() {
+        return mTelecomServiceImpl;
+    }
+}
diff --git a/src/com/android/server/telecom/CallActivity.java b/src/com/android/server/telecom/UserCallIntentProcessor.java
similarity index 63%
rename from src/com/android/server/telecom/CallActivity.java
rename to src/com/android/server/telecom/UserCallIntentProcessor.java
index 37e24f6..36d2126 100644
--- a/src/com/android/server/telecom/CallActivity.java
+++ b/src/com/android/server/telecom/UserCallIntentProcessor.java
@@ -16,19 +16,16 @@
 
 package com.android.server.telecom;
 
-import android.app.Activity;
+import com.android.server.telecom.components.PrimaryCallReceiver;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Bundle;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
-import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.widget.Toast;
@@ -36,7 +33,7 @@
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
- * Activity that handles system CALL actions and forwards them to {@link CallReceiver}.
+ * Handles system CALL actions and forwards them to {@link CallIntentProcessor}.
  * Handles all three CALL action types: CALL, CALL_PRIVILEGED, and CALL_EMERGENCY.
  *
  * Pre-L, the only way apps were were allowed to make outgoing emergency calls was the
@@ -49,23 +46,15 @@
  * {@link android.telecom.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 CallActivity}. Calling startActivity will continue to work on all non-emergency numbers
- * just like it did pre-L.
+ * {@link UserCallIntentProcessor}. Calling startActivity will continue to work on all
+ * non-emergency numbers just like it did pre-L.
  */
-public class CallActivity extends Activity {
+public class UserCallIntentProcessor {
 
-    @Override
-    protected void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
+    private final Context mContext;
 
-        // TODO: Figure out if there is something to restore from bundle.
-        // See OutgoingCallBroadcaster in services/Telephony for more.
-
-        processIntent(getIntent());
-
-        // This activity does not have associated UI, so close.
-        finish();
-        Log.d(this, "onCreate: end");
+    public UserCallIntentProcessor(Context context) {
+        mContext = context;
     }
 
     /**
@@ -73,34 +62,22 @@
      *
      * @param intent The intent.
      */
-    private void processIntent(Intent intent) {
+    public void processIntent(Intent intent, String callingPackageName) {
         // Ensure call intents are not processed on devices that are not capable of calling.
         if (!isVoiceCapable()) {
             return;
         }
 
-        verifyCallAction(intent);
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
                 Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                 Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            processOutgoingCallIntent(intent);
+            processOutgoingCallIntent(intent, callingPackageName);
         }
     }
 
-    private void verifyCallAction(Intent intent) {
-        if (CallActivity.class.getName().equals(intent.getComponent().getClassName())) {
-            // If we were launched directly from the CallActivity, not one of its more privileged
-            // aliases, then make sure that only the non-privileged actions are allowed.
-            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
-                Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
-                intent.setAction(Intent.ACTION_CALL);
-            }
-        }
-    }
-
-    private void processOutgoingCallIntent(Intent intent) {
+    private void processOutgoingCallIntent(Intent intent, String callingPackageName) {
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
         String uriString = handle.getSchemeSpecificPart();
@@ -110,33 +87,34 @@
                     PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
         }
 
-        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
-                && !TelephonyUtil.shouldProcessAsEmergency(this, handle)) {
+                && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
             // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
             // restriction.
-            Toast.makeText(this, getResources().getString(R.string.outgoing_call_not_allowed),
+            Toast.makeText(
+                    mContext,
+                    mContext.getResources().getString(R.string.outgoing_call_not_allowed),
                     Toast.LENGTH_SHORT).show();
             Log.d(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
                     + "restriction");
             return;
         }
 
-        intent.putExtra(CallReceiver.KEY_IS_DEFAULT_DIALER, isDefaultDialer());
+        intent.putExtra(CallIntentProcessor.KEY_IS_DEFAULT_DIALER, isDefaultDialer(callingPackageName));
         sendBroadcastToReceiver(intent);
     }
 
-    private boolean isDefaultDialer() {
-        final String packageName = getCallingPackage();
-        if (TextUtils.isEmpty(packageName)) {
+    private boolean isDefaultDialer(String callingPackageName) {
+        if (TextUtils.isEmpty(callingPackageName)) {
             return false;
         }
 
         final TelecomManager telecomManager =
-                (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+                (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
         final ComponentName defaultPhoneApp = telecomManager.getDefaultPhoneApp();
         return (defaultPhoneApp != null
-                && TextUtils.equals(defaultPhoneApp.getPackageName(), packageName));
+                && TextUtils.equals(defaultPhoneApp.getPackageName(), callingPackageName));
     }
 
     /**
@@ -145,7 +123,7 @@
      * @return {@code True} if the device is voice-capable.
      */
     private boolean isVoiceCapable() {
-        return getApplicationContext().getResources().getBoolean(
+        return mContext.getApplicationContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_voice_capable);
     }
 
@@ -153,11 +131,11 @@
      * Trampolines the intent to the broadcast receiver that runs only as the primary user.
      */
     private boolean sendBroadcastToReceiver(Intent intent) {
-        intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, false);
+        intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setClass(this, CallReceiver.class);
+        intent.setClass(mContext, PrimaryCallReceiver.class);
         Log.d(this, "Sending broadcast as user to CallReceiver");
-        sendBroadcastAsUser(intent, UserHandle.OWNER);
+        mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
         return true;
     }
 }
diff --git a/src/com/android/server/telecom/components/BluetoothPhoneService.java b/src/com/android/server/telecom/components/BluetoothPhoneService.java
new file mode 100644
index 0000000..1f757d5
--- /dev/null
+++ b/src/com/android/server/telecom/components/BluetoothPhoneService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.TelecomSystem;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
+ * and accepts call-related commands to perform on behalf of the BT device.
+ */
+public final class BluetoothPhoneService extends Service implements TelecomSystem.Component {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
+    }
+
+    @Override
+    public TelecomSystem getTelecomSystem() {
+        return TelecomSystem.getInstance();
+    }
+}
diff --git a/src/com/android/server/telecom/ErrorDialogActivity.java b/src/com/android/server/telecom/components/ErrorDialogActivity.java
similarity index 97%
rename from src/com/android/server/telecom/ErrorDialogActivity.java
rename to src/com/android/server/telecom/components/ErrorDialogActivity.java
index a669f10..858682e 100644
--- a/src/com/android/server/telecom/ErrorDialogActivity.java
+++ b/src/com/android/server/telecom/components/ErrorDialogActivity.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.telecom;
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.Log;
+import com.android.server.telecom.R;
 
 import android.app.Activity;
 import android.app.AlertDialog;
diff --git a/src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java b/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
similarity index 96%
rename from src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java
rename to src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
index 9634eda..7737cd8 100644
--- a/src/com/android/server/telecom/PhoneAccountBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
@@ -14,7 +14,9 @@
  * limitations under the License
  */
 
-package com.android.server.telecom;
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.PhoneAccountRegistrar;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
new file mode 100644
index 0000000..8e1f72d
--- /dev/null
+++ b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
@@ -0,0 +1,27 @@
+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;
+import android.content.Intent;
+
+/**
+ * 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 PrimaryCallReceiver} which interacts with the rest of Telecom, both of which run only as
+ * the primary user.
+ */
+public class PrimaryCallReceiver extends BroadcastReceiver implements TelecomSystem.Component {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getTelecomSystem().getCallIntentProcessor().processIntent(intent);
+    }
+
+    @Override
+    public TelecomSystem getTelecomSystem() {
+        return TelecomSystem.getInstance();
+    }
+}
diff --git a/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
new file mode 100644
index 0000000..b4f525c
--- /dev/null
+++ b/src/com/android/server/telecom/components/TelecomBroadcastReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.TelecomSystem;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Handles miscellaneous Telecom broadcast intents. This should be visible from outside, but
+ * should not be in the "exported" state.
+ */
+public final class TelecomBroadcastReceiver
+        extends BroadcastReceiver implements TelecomSystem.Component {
+
+    /** {@inheritDoc} */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getTelecomSystem().getTelecomBroadcastIntentProcessor().processIntent(intent);
+    }
+
+    @Override
+    public TelecomSystem getTelecomSystem() {
+        return TelecomSystem.getInstance();
+    }
+
+}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
new file mode 100644
index 0000000..0b8238b
--- /dev/null
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.components;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.server.telecom.Log;
+import com.android.server.telecom.TelecomSystem;
+
+/**
+ * Implementation of the ITelecom interface.
+ */
+public class TelecomService extends Service implements TelecomSystem.Component {
+
+    @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));
+        // Start the BluetoothPhoneService
+        if (BluetoothAdapter.getDefaultAdapter() != null) {
+            startService(new Intent(this, BluetoothPhoneService.class));
+        }
+        return getTelecomSystem().getTelecomServiceImpl().getBinder();
+    }
+
+    @Override
+    public TelecomSystem getTelecomSystem() {
+        return TelecomSystem.getInstance();
+    }
+}
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
new file mode 100644
index 0000000..3ea92bd
--- /dev/null
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.components;
+
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.UserCallIntentProcessor;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+
+/**
+ * Activity that handles system CALL actions and forwards them to {@link CallIntentProcessor}.
+ * Handles all three CALL action types: CALL, CALL_PRIVILEGED, and CALL_EMERGENCY.
+ *
+ * Pre-L, the only way apps were were allowed to make outgoing emergency calls was the
+ * ACTION_CALL_PRIVILEGED action (which requires the system only CALL_PRIVILEGED permission).
+ *
+ * In L, any app that has the CALL_PRIVILEGED permission can continue to make outgoing emergency
+ * calls via ACTION_CALL_PRIVILEGED.
+ *
+ * In addition, the default dialer (identified via
+ * {@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.
+ */
+public class UserCallActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        // TODO: Figure out if there is something to restore from bundle.
+        // See OutgoingCallBroadcaster in services/Telephony for more.
+        Intent intent = getIntent();
+        verifyCallAction(intent);
+        new UserCallIntentProcessor(this).processIntent(getIntent(), getCallingPackage());
+        finish();
+    }
+
+    private void verifyCallAction(Intent intent) {
+        if (getClass().getName().equals(intent.getComponent().getClassName())) {
+            // If we were launched directly from the CallActivity, not one of its more privileged
+            // aliases, then make sure that only the non-privileged actions are allowed.
+            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
+                Log.w(this, "Attempt to deliver non-CALL action; forcing to CALL");
+                intent.setAction(Intent.ACTION_CALL);
+            }
+        }
+    }
+}