CallManager improvements

- Merging TelecomUiCallManager code into UiCallManager. Also
  UiCallManager is no longer a singleton and is constructed/owned by
  TelecomActivity. TelecomActivity passes it to Fragments.
- Moved InCallServiceImpl into parent telecom package and killed
  embedded sub-package.

Bug: 37251324
Test: Tested incoming, outgoing calls, including conference.
Change-Id: I2817a3a93f8314f303e53394d665266dc39a04b5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0d3c89a..646cc00 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -57,7 +57,7 @@
                     android:resource="@xml/searchable" />
         </activity>
 
-        <service android:name="com.android.car.dialer.telecom.embedded.InCallServiceImpl"
+        <service android:name="com.android.car.dialer.telecom.InCallServiceImpl"
                  android:permission="android.permission.BIND_INCALL_SERVICE"
                  android:exported="true">
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
diff --git a/src/com/android/car/dialer/DialerFragment.java b/src/com/android/car/dialer/DialerFragment.java
index edf163a..e84f189 100644
--- a/src/com/android/car/dialer/DialerFragment.java
+++ b/src/com/android/car/dialer/DialerFragment.java
@@ -63,6 +63,7 @@
     }
 
     private Context mContext;
+    private UiCallManager mUiCallManager;
     private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
     private AudioManager mAudioManager;
     private ToneGenerator mToneGenerator;
@@ -86,6 +87,12 @@
         void onDialerBackClick();
     }
 
+    public static DialerFragment newInstance(UiCallManager callManager) {
+        DialerFragment fragment = new DialerFragment();
+        fragment.mUiCallManager = callManager;
+        return fragment;
+    }
+
     /**
      * Sets the given {@link DialerBackButtonListener} to be notified whenever the back button
      * on the dialer has been clicked. Passing {@code null} to this method will clear all listeners.
@@ -145,7 +152,7 @@
                 }
 
                 if (!TextUtils.isEmpty(mNumber.toString())) {
-                    getUiCallManager().safePlaceCall(mNumber.toString(), false);
+                    mUiCallManager.safePlaceCall(mNumber.toString(), false);
                 }
             });
             View deleteButton = view.findViewById(R.id.delete);
@@ -207,7 +214,7 @@
                 mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
             }
         }
-        UiCallManager.getInstance(mContext).addListener(mCallListener);
+        mUiCallManager.addListener(mCallListener);
 
         if (mPendingRunnable != null) {
             mPendingRunnable.run();
@@ -218,7 +225,7 @@
     @Override
     public void onPause() {
         super.onPause();
-        UiCallManager.getInstance(mContext).removeListener(mCallListener);
+        mUiCallManager.removeListener(mCallListener);
         stopTone();
         synchronized (mToneGeneratorLock) {
             if (mToneGenerator != null) {
@@ -272,10 +279,6 @@
         }
     };
 
-    private UiCallManager getUiCallManager() {
-        return UiCallManager.getInstance(mContext);
-    }
-
     private String getFormattedNumber(String number) {
         return TelecomUtils.getFormattedNumber(mContext, number);
     }
@@ -286,7 +289,7 @@
             if (event.getKeyCode() == KeyEvent.KEYCODE_CALL &&
                     event.getAction() == KeyEvent.ACTION_UP &&
                     !TextUtils.isEmpty(mNumber.toString())) {
-                getUiCallManager().safePlaceCall(mNumber.toString(), false);
+                mUiCallManager.safePlaceCall(mNumber.toString(), false);
             }
         }
     };
diff --git a/src/com/android/car/dialer/OngoingCallFragment.java b/src/com/android/car/dialer/OngoingCallFragment.java
index 2eb659e..a083b80 100644
--- a/src/com/android/car/dialer/OngoingCallFragment.java
+++ b/src/com/android/car/dialer/OngoingCallFragment.java
@@ -105,15 +105,17 @@
             mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
     private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator(10);
 
-    // Should be set soon after construction.
-    void setUiBluetoothMonitor(UiBluetoothMonitor uiBluetoothMonitor) {
-        mUiBluetoothMonitor = uiBluetoothMonitor;
+    public static OngoingCallFragment newInstance(
+            UiCallManager callManager, UiBluetoothMonitor btMonitor) {
+        OngoingCallFragment fragment = new OngoingCallFragment();
+        fragment.mUiCallManager = callManager;
+        fragment.mUiBluetoothMonitor = btMonitor;
+        return fragment;
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mUiCallManager = UiCallManager.getInstance(getContext());
         mHandler = new Handler();
     }
 
@@ -123,7 +125,6 @@
         mHandler.removeCallbacks(mUpdateDurationRunnable);
         mHandler.removeCallbacks(mStopDtmfToneRunnable);
         mHandler = null;
-        mUiCallManager = null;
         mLoadedNumber = null;
     }
 
diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java
index f2719a7..78f4d3e 100644
--- a/src/com/android/car/dialer/StrequentsAdapter.java
+++ b/src/com/android/car/dialer/StrequentsAdapter.java
@@ -56,6 +56,7 @@
     private static final int VIEW_TYPE_STREQUENT = 2;
 
     private final Context mContext;
+    private final UiCallManager mUiCallManager;
     private List<ContactEntry> mData;
 
     private LastCallData mLastCallData;
@@ -73,8 +74,9 @@
     private int mMaxItems = -1;
     private boolean mIsEmpty;
 
-    public StrequentsAdapter(Context context) {
+    public StrequentsAdapter(Context context, UiCallManager callManager) {
         mContext = context;
+        mUiCallManager = callManager;
         mContentResolver = context.getContentResolver();
     }
 
@@ -315,7 +317,7 @@
             secondaryText.append(relativeDate);
         }
 
-        int[] callTypes = getCarTelecomManager().getCallTypes(cursor, 1);
+        int[] callTypes = mUiCallManager.getCallTypes(cursor, 1);
 
         return new LastCallData(number, nameSb.toString(), secondaryText.toString(), callTypes);
     }
@@ -394,10 +396,6 @@
                 DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
     }
 
-    private UiCallManager getCarTelecomManager() {
-        return UiCallManager.getInstance(mContext);
-    }
-
     /**
      * A container for data relating to a last call entry.
      */
diff --git a/src/com/android/car/dialer/StrequentsFragment.java b/src/com/android/car/dialer/StrequentsFragment.java
index 87bb668..364c25d 100644
--- a/src/com/android/car/dialer/StrequentsFragment.java
+++ b/src/com/android/car/dialer/StrequentsFragment.java
@@ -48,6 +48,7 @@
     public static final String KEY_MAX_CLICKS = "max_clicks";
     public static final int DEFAULT_MAX_CLICKS = 6;
 
+    private UiCallManager mUiCallManager;
     private StrequentsAdapter mAdapter;
     private CursorLoader mSpeedialCursorLoader;
     private CursorLoader mCallLogCursorLoader;
@@ -57,6 +58,12 @@
     private Cursor mCallLogCursor;
     private boolean mHasLoadedData;
 
+    public static StrequentsFragment newInstance(UiCallManager callManager) {
+        StrequentsFragment fragment = new StrequentsFragment();
+        fragment.mUiCallManager = callManager;
+        return fragment;
+    }
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -120,14 +127,13 @@
 
         mListView.removeDefaultItemDecoration();
         mListView.setLightMode();
-        mAdapter = new StrequentsAdapter(mContext);
+        mAdapter = new StrequentsAdapter(mContext, mUiCallManager);
         mAdapter.setStrequentsListener(viewHolder -> {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "onContactedClicked");
             }
 
-            UiCallManager.getInstance(mContext).safePlaceCall(
-                    (String) viewHolder.itemView.getTag(), false);
+            mUiCallManager.safePlaceCall((String) viewHolder.itemView.getTag(), false);
         });
         mListView.setMaxPages(maxPages);
         mListView.setAdapter(mAdapter);
diff --git a/src/com/android/car/dialer/TelecomActivity.java b/src/com/android/car/dialer/TelecomActivity.java
index 499f6f1..43fce86 100644
--- a/src/com/android/car/dialer/TelecomActivity.java
+++ b/src/com/android/car/dialer/TelecomActivity.java
@@ -91,7 +91,7 @@
         getWindow().getDecorView().setBackgroundColor(getColor(R.color.phone_theme));
         setTitle(getString(R.string.phone_app_name));
 
-        mUiCallManager = UiCallManager.getInstance(this);
+        mUiCallManager = new UiCallManager(this);
         mUiBluetoothMonitor = new UiBluetoothMonitor(this);
 
         if (savedInstanceState != null) {
@@ -308,7 +308,7 @@
         }
 
         if (mSpeedDialFragment == null) {
-            mSpeedDialFragment = new StrequentsFragment();
+            mSpeedDialFragment = StrequentsFragment.newInstance(mUiCallManager);
             Bundle args = new Bundle();
             mSpeedDialFragment.setArguments(args);
         }
@@ -330,9 +330,8 @@
         }
 
         if (mOngoingCallFragment == null) {
-            OngoingCallFragment fragment = new OngoingCallFragment();
-            fragment.setUiBluetoothMonitor(mUiBluetoothMonitor);
-            mOngoingCallFragment = fragment;
+            mOngoingCallFragment =
+                    OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor);
         }
 
         setContentFragmentWithFadeAnimation(mOngoingCallFragment);
@@ -356,7 +355,7 @@
                 Log.v(TAG, "showDialer: creating dialer");
             }
 
-            mDialerFragment = new DialerFragment();
+            mDialerFragment = DialerFragment.newInstance(mUiCallManager);
             mDialerFragment.setDialerBackButtonListener(this);
         }
 
diff --git a/src/com/android/car/dialer/telecom/embedded/InCallServiceImpl.java b/src/com/android/car/dialer/telecom/InCallServiceImpl.java
similarity index 97%
rename from src/com/android/car/dialer/telecom/embedded/InCallServiceImpl.java
rename to src/com/android/car/dialer/telecom/InCallServiceImpl.java
index 26e748c..4868896 100644
--- a/src/com/android/car/dialer/telecom/embedded/InCallServiceImpl.java
+++ b/src/com/android/car/dialer/telecom/InCallServiceImpl.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.dialer.telecom.embedded;
+package com.android.car.dialer.telecom;
 
 import android.content.Intent;
 import android.os.Binder;
@@ -28,7 +28,7 @@
 
 /**
  * An implementation of {@link InCallService}. This service is bounded by android telecom and
- * {@link TelecomUiCallManager}. For incoming calls it will launch Dialer app.
+ * {@link UiCallManager}. For incoming calls it will launch Dialer app.
  */
 public class InCallServiceImpl extends InCallService {
     private static final String TAG = "Em.InCallService";
diff --git a/src/com/android/car/dialer/telecom/UiCallManager.java b/src/com/android/car/dialer/telecom/UiCallManager.java
index 8b24f2a..95e5139 100644
--- a/src/com/android/car/dialer/telecom/UiCallManager.java
+++ b/src/com/android/car/dialer/telecom/UiCallManager.java
@@ -15,43 +15,49 @@
  */
 package com.android.car.dialer.telecom;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.database.Cursor;
+import android.net.Uri;
+import android.os.IBinder;
 import android.provider.CallLog;
 import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
+import android.telecom.GatewayInfo;
+import android.telecom.InCallService;
+import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
 import com.android.car.dialer.R;
-import com.android.car.dialer.telecom.embedded.TelecomUiCallManager;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * The entry point for all interactions between UI and telecom.
  */
-public abstract class UiCallManager {
+public class UiCallManager {
     private static String TAG = "Em.TelecomMgr";
 
-    private static Object sInstanceLock = new Object();
-    private static UiCallManager sInstance;
-
-    private long mLastPlacedCallTimeMs = 0;
-
     // Rate limit how often you can place outgoing calls.
     private static final long MIN_TIME_BETWEEN_CALLS_MS = 3000;
-
     private static final List<Integer> sCallStateRank = new ArrayList<>();
 
-    protected Context mContext;
-
-    protected TelephonyManager mTelephonyManager;
+    // Used to assign id's to UiCall objects as they're created.
+    private static int nextCarPhoneCallId = 0;
 
     static {
         // States should be added from lowest rank to highest
@@ -66,69 +72,365 @@
         sCallStateRank.add(Call.STATE_RINGING);
     }
 
-    public static UiCallManager getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Creating an instance of CarTelecomManager");
-                }
-                sInstance = new TelecomUiCallManager();
-                sInstance.setUp(context.getApplicationContext());
-            }
-        }
-        return sInstance;
-    }
+    private Context mContext;
+    private TelephonyManager mTelephonyManager;
+    private long mLastPlacedCallTimeMs;
 
-    protected UiCallManager() {}
+    private TelecomManager mTelecomManager;
+    private InCallServiceImpl mInCallService;
+    private final Map<UiCall, Call> mCallMapping = new HashMap<>();
+    private final List<CallListener> mCallListeners = new CopyOnWriteArrayList<>();
 
-    protected void setUp(Context context) {
+    public UiCallManager(Context context) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "SetUp");
         }
 
         mContext = context;
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+        mTelecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+        Intent intent = new Intent(context, InCallServiceImpl.class);
+        intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
+        context.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
-    public abstract void tearDown();
+    private final ServiceConnection mInCallServiceConnection = new ServiceConnection() {
 
-    public abstract void addListener(CallListener listener);
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder binder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onServiceConnected: " + name + ", service: " + binder);
+            }
+            mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService();
+            mInCallService.registerCallback(mInCallServiceCallback);
 
-    public abstract void removeListener(CallListener listener);
+            // The InCallServiceImpl could be bound when we already have some active calls, let's
+            // notify UI about these calls.
+            for (Call telecomCall : mInCallService.getCalls()) {
+                UiCall uiCall = doTelecomCallAdded(telecomCall);
+                onStateChanged(uiCall, uiCall.getState());
+            }
+        }
 
-    protected abstract void placeCall(String number);
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onServiceDisconnected: " + name);
+            }
+            mInCallService.unregisterCallback(mInCallServiceCallback);
+        }
 
-    public abstract void answerCall(UiCall call);
+        private InCallServiceImpl.Callback mInCallServiceCallback =
+                new InCallServiceImpl.Callback() {
+                    @Override
+                    public void onTelecomCallAdded(Call telecomCall) {
+                        doTelecomCallAdded(telecomCall);
+                    }
 
-    public abstract void rejectCall(UiCall call, boolean rejectWithMessage, String textMessage);
+                    @Override
+                    public void onTelecomCallRemoved(Call telecomCall) {
+                        doTelecomCallRemoved(telecomCall);
+                    }
 
-    public abstract void disconnectCall(UiCall call);
+                    @Override
+                    public void onCallAudioStateChanged(CallAudioState audioState) {
+                        doCallAudioStateChanged(audioState);
+                    }
+                };
+    };
 
-    public abstract List<UiCall> getCalls();
+    public void tearDown() {
+        if (mInCallService != null) {
+            mContext.unbindService(mInCallServiceConnection);
+            mInCallService = null;
+        }
+        mCallMapping.clear();
+    }
 
-    public abstract boolean getMuted();
+    public void addListener(CallListener listener) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "addListener: " + listener);
+        }
+        mCallListeners.add(listener);
+    }
 
-    public abstract void setMuted(boolean muted);
+    public void removeListener(CallListener listener) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "removeListener: " + listener);
+        }
+        mCallListeners.remove(listener);
+    }
 
-    public abstract int getSupportedAudioRouteMask();
+    protected void placeCall(String number) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "placeCall: " + number);
+        }
+        Uri uri = Uri.fromParts("tel", number, null);
+        Log.d(TAG, "android.telecom.TelecomManager#placeCall: " + uri);
+        mTelecomManager.placeCall(uri, null);
+    }
 
-    public abstract int getAudioRoute();
+    public void answerCall(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "answerCall: " + uiCall);
+        }
 
-    public abstract void setAudioRoute(int audioRoute);
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.answer(0);
+        }
+    }
 
-    public abstract void holdCall(UiCall call);
+    public void rejectCall(UiCall uiCall, boolean rejectWithMessage, String textMessage) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "rejectCall: " + uiCall + ", rejectWithMessage: " + rejectWithMessage
+                    + "textMessage: " + textMessage);
+        }
 
-    public abstract void unholdCall(UiCall call);
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.reject(rejectWithMessage, textMessage);
+        }
+    }
 
-    public abstract void playDtmfTone(UiCall call, char digit);
+    public void disconnectCall(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "disconnectCall: " + uiCall);
+        }
 
-    public abstract void stopDtmfTone(UiCall call);
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.disconnect();
+        }
+    }
 
-    public abstract void postDialContinue(UiCall call, boolean proceed);
+    public List<UiCall> getCalls() {
+        return new ArrayList<>(mCallMapping.keySet());
+    }
 
-    public abstract void conference(UiCall call, UiCall otherCall);
+    public boolean getMuted() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "getMuted");
+        }
+        if (mInCallService == null) {
+            return false;
+        }
+        CallAudioState audioState = mInCallService.getCallAudioState();
+        return audioState != null && audioState.isMuted();
+    }
 
-    public abstract void splitFromConference(UiCall call);
+    public void setMuted(boolean muted) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "setMuted: " + muted);
+        }
+        if (mInCallService == null) {
+            return;
+        }
+        mInCallService.setMuted(muted);
+    }
+
+    public int getSupportedAudioRouteMask() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "getSupportedAudioRouteMask");
+        }
+
+        CallAudioState audioState = getCallAudioStateOrNull();
+        return audioState != null ? audioState.getSupportedRouteMask() : 0;
+    }
+
+    public int getAudioRoute() {
+        CallAudioState audioState = getCallAudioStateOrNull();
+        int audioRoute = audioState != null ? audioState.getRoute() : 0;
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "getAudioRoute " + audioRoute);
+        }
+        return audioRoute;
+    }
+
+    public void setAudioRoute(int audioRoute) {
+        // In case of embedded where the CarKitt is always connected to one kind of speaker we
+        // should simply ignore any setAudioRoute requests.
+        Log.w(TAG, "setAudioRoute ignoring request " + audioRoute);
+    }
+
+    public void holdCall(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "holdCall: " + uiCall);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.hold();
+        }
+    }
+
+    public void unholdCall(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "unholdCall: " + uiCall);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.unhold();
+        }
+    }
+
+    public void playDtmfTone(UiCall uiCall, char digit) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "playDtmfTone: call: " + uiCall + ", digit: " + digit);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.playDtmfTone(digit);
+        }
+    }
+
+    public void stopDtmfTone(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "stopDtmfTone: call: " + uiCall);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.stopDtmfTone();
+        }
+    }
+
+    public void postDialContinue(UiCall uiCall, boolean proceed) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "postDialContinue: call: " + uiCall + ", proceed: " + proceed);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.postDialContinue(proceed);
+        }
+    }
+
+    public void conference(UiCall uiCall, UiCall otherUiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "conference: call: " + uiCall + ", otherCall: " + otherUiCall);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        Call otherTelecomCall = mCallMapping.get(otherUiCall);
+        if (telecomCall != null) {
+            telecomCall.conference(otherTelecomCall);
+        }
+    }
+
+    public void splitFromConference(UiCall uiCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "splitFromConference: call: " + uiCall);
+        }
+
+        Call telecomCall = mCallMapping.get(uiCall);
+        if (telecomCall != null) {
+            telecomCall.splitFromConference();
+        }
+    }
+
+    private UiCall doTelecomCallAdded(final Call telecomCall) {
+        Log.d(TAG, "doTelecomCallAdded: " + telecomCall);
+
+        UiCall uiCall = getOrCreateCallContainer(telecomCall);
+        telecomCall.registerCallback(new TelecomCallListener(this, uiCall));
+        for (CallListener listener : mCallListeners) {
+            listener.onCallAdded(uiCall);
+        }
+        Log.d(TAG, "Call backs registered");
+
+        if (telecomCall.getState() == Call.STATE_SELECT_PHONE_ACCOUNT) {
+            // TODO(b/26189994): need to show Phone Account picker to let user choose a phone
+            // account. It should be an account from TelecomManager#getCallCapablePhoneAccounts
+            // list.
+            Log.w(TAG, "Need to select phone account for the given call: " + telecomCall + ", "
+                    + "but this feature is not implemented yet.");
+            telecomCall.disconnect();
+        }
+        return uiCall;
+    }
+
+    private void doTelecomCallRemoved(Call telecomCall) {
+        UiCall uiCall = getOrCreateCallContainer(telecomCall);
+
+        mCallMapping.remove(uiCall);
+
+        for (CallListener listener : mCallListeners) {
+            listener.onCallRemoved(uiCall);
+        }
+    }
+
+    private void doCallAudioStateChanged(CallAudioState audioState) {
+        for (CallListener listener : mCallListeners) {
+            listener.onAudioStateChanged(audioState.isMuted(), audioState.getRoute(),
+                    audioState.getSupportedRouteMask());
+        }
+    }
+
+    private void onStateChanged(UiCall uiCall, int state) {
+        for (CallListener listener : mCallListeners) {
+            listener.onStateChanged(uiCall, state);
+        }
+    }
+
+    private void onCallUpdated(UiCall uiCall) {
+        for (CallListener listener : mCallListeners) {
+            listener.onCallUpdated(uiCall);
+        }
+    }
+
+    private UiCall getOrCreateCallContainer(Call telecomCall) {
+        for (Map.Entry<UiCall, Call> entry : mCallMapping.entrySet()) {
+            if (entry.getValue() == telecomCall) {
+                return entry.getKey();
+            }
+        }
+
+        UiCall uiCall = new UiCall(nextCarPhoneCallId++);
+        updateCallContainerFromTelecom(uiCall, telecomCall);
+        mCallMapping.put(uiCall, telecomCall);
+        return uiCall;
+    }
+
+    private static void updateCallContainerFromTelecom(UiCall uiCall, Call telecomCall) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "updateCallContainerFromTelecom: call: " + uiCall + ", telecomCall: "
+                    + telecomCall);
+        }
+
+        uiCall.setState(telecomCall.getState());
+        uiCall.setHasChildren(!telecomCall.getChildren().isEmpty());
+        uiCall.setHasParent(telecomCall.getParent() != null);
+
+        Call.Details details = telecomCall.getDetails();
+        if (details == null) {
+            return;
+        }
+
+        uiCall.setConnectTimeMillis(details.getConnectTimeMillis());
+
+        DisconnectCause cause = details.getDisconnectCause();
+        uiCall.setDisconnectCause(cause == null ? null : cause.getLabel());
+
+        GatewayInfo gatewayInfo = details.getGatewayInfo();
+        uiCall.setGatewayInfoOriginalAddress(
+                gatewayInfo == null ? null : gatewayInfo.getOriginalAddress());
+
+        String number = "";
+        if (gatewayInfo != null) {
+            number = gatewayInfo.getOriginalAddress().getSchemeSpecificPart();
+        } else if (details.getHandle() != null) {
+            number = details.getHandle().getSchemeSpecificPart();
+        }
+        uiCall.setNumber(number);
+    }
+
+    private CallAudioState getCallAudioStateOrNull() {
+        return mInCallService != null ? mInCallService.getCallAudioState() : null;
+    }
 
     public static class CallListener {
         @SuppressWarnings("unused")
@@ -308,4 +610,69 @@
             }
         };
     }
+
+    private static class TelecomCallListener extends Call.Callback {
+        private final WeakReference<UiCallManager> mCarTelecomMangerRef;
+        private final WeakReference<UiCall> mCallContainerRef;
+
+        TelecomCallListener(UiCallManager carTelecomManager, UiCall uiCall) {
+            mCarTelecomMangerRef = new WeakReference<>(carTelecomManager);
+            mCallContainerRef = new WeakReference<>(uiCall);
+        }
+
+        @Override
+        public void onStateChanged(Call telecomCall, int state) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onStateChanged: " + state);
+            }
+            UiCallManager manager = mCarTelecomMangerRef.get();
+            UiCall call = mCallContainerRef.get();
+            if (manager != null && call != null) {
+                call.setState(state);
+                manager.onStateChanged(call, state);
+            }
+        }
+
+        @Override
+        public void onParentChanged(Call telecomCall, Call parent) {
+            doCallUpdated(telecomCall);
+        }
+
+        @Override
+        public void onCallDestroyed(Call telecomCall) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCallDestroyed");
+            }
+        }
+
+        @Override
+        public void onDetailsChanged(Call telecomCall, Call.Details details) {
+            doCallUpdated(telecomCall);
+        }
+
+        @Override
+        public void onVideoCallChanged(Call telecomCall, InCallService.VideoCall videoCall) {
+            doCallUpdated(telecomCall);
+        }
+
+        @Override
+        public void onCannedTextResponsesLoaded(Call telecomCall,
+                List<String> cannedTextResponses) {
+            doCallUpdated(telecomCall);
+        }
+
+        @Override
+        public void onChildrenChanged(Call telecomCall, List<Call> children) {
+            doCallUpdated(telecomCall);
+        }
+
+        private void doCallUpdated(Call telecomCall) {
+            UiCallManager manager = mCarTelecomMangerRef.get();
+            UiCall uiCall = mCallContainerRef.get();
+            if (manager != null && uiCall != null) {
+                updateCallContainerFromTelecom(uiCall, telecomCall);
+                manager.onCallUpdated(uiCall);
+            }
+        }
+    }
 }
diff --git a/src/com/android/car/dialer/telecom/embedded/TelecomUiCallManager.java b/src/com/android/car/dialer/telecom/embedded/TelecomUiCallManager.java
deleted file mode 100644
index 357512f..0000000
--- a/src/com/android/car/dialer/telecom/embedded/TelecomUiCallManager.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2015 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.car.dialer.telecom.embedded;
-
-import com.android.car.dialer.telecom.UiCall;
-import com.android.car.dialer.telecom.UiCallManager;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.IBinder;
-import android.telecom.Call;
-import android.telecom.Call.Details;
-import android.telecom.CallAudioState;
-import android.telecom.DisconnectCause;
-import android.telecom.GatewayInfo;
-import android.telecom.InCallService.VideoCall;
-import android.telecom.TelecomManager;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * An implementation of {@link UiCallManager} that uses {@code android.telecom.*} stack.
- */
-public class TelecomUiCallManager extends UiCallManager {
-
-    private static final String TAG = "Em.TelecomUiCallMgr";
-
-    // Used to assign id's to UiCall objects as they're created.
-    private static int nextCarPhoneCallId = 0;
-
-    private TelecomManager mTelecomManager;
-    private InCallServiceImpl mInCallService;
-    private Map<UiCall, Call> mCallMapping = new HashMap<>();
-
-    private List<CallListener> mCallListeners = new CopyOnWriteArrayList<>();
-
-    @Override
-    protected void setUp(Context context) {
-        super.setUp(context);
-        mTelecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
-        Intent intent = new Intent(context, InCallServiceImpl.class);
-        intent.setAction(InCallServiceImpl.ACTION_LOCAL_BIND);
-        context.bindService(intent, mInCallServiceConnection, Context.BIND_AUTO_CREATE);
-    }
-
-    public void tearDown() {
-        if (mInCallService != null) {
-            mContext.unbindService(mInCallServiceConnection);
-            mInCallService = null;
-        }
-        mCallMapping.clear();
-    }
-
-    @Override
-    public void addListener(CallListener listener) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "addListener: " + listener);
-        }
-        mCallListeners.add(listener);
-    }
-
-    @Override
-    public void removeListener(CallListener listener) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "removeListener: " + listener);
-        }
-        mCallListeners.remove(listener);
-    }
-
-    @Override
-    public void placeCall(String number) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "placeCall: " + number);
-        }
-        Uri uri = Uri.fromParts("tel", number, null);
-        Log.d(TAG, "android.telecom.TelecomManager#placeCall: " + uri);
-        mTelecomManager.placeCall(uri, null);
-    }
-
-    @Override
-    public void answerCall(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "answerCall: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.answer(0);
-        }
-    }
-
-    @Override
-    public void rejectCall(UiCall uiCall, boolean rejectWithMessage, String textMessage) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "rejectCall: " + uiCall + ", rejectWithMessage: " + rejectWithMessage
-                    + "textMessage: " + textMessage);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.reject(rejectWithMessage, textMessage);
-        }
-    }
-
-    @Override
-    public void disconnectCall(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "disconnectCall: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.disconnect();
-        }
-    }
-
-    @Override
-    public List<UiCall> getCalls() {
-        return new ArrayList<>(mCallMapping.keySet());
-    }
-
-    @Override
-    public boolean getMuted() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getMuted");
-        }
-        if (mInCallService == null) {
-            return false;
-        }
-        CallAudioState audioState = mInCallService.getCallAudioState();
-        return audioState != null && audioState.isMuted();
-    }
-
-    @Override
-    public void setMuted(boolean muted) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "setMuted: " + muted);
-        }
-        if (mInCallService == null) {
-            return;
-        }
-        mInCallService.setMuted(muted);
-    }
-
-    @Override
-    public int getSupportedAudioRouteMask() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getSupportedAudioRouteMask");
-        }
-
-        CallAudioState audioState = getCallAudioStateOrNull();
-        return audioState != null ? audioState.getSupportedRouteMask() : 0;
-    }
-
-    @Override
-    public int getAudioRoute() {
-        CallAudioState audioState = getCallAudioStateOrNull();
-        int audioRoute = audioState != null ? audioState.getRoute() : 0;
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "getAudioRoute " + audioRoute);
-        }
-        return audioRoute;
-    }
-
-    @Override
-    public void setAudioRoute(int audioRoute) {
-        // In case of embedded where the CarKitt is always connected to one kind of speaker we
-        // should simply ignore any setAudioRoute requests.
-        Log.w(TAG, "setAudioRoute ignoring request " + audioRoute);
-    }
-
-    @Override
-    public void holdCall(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "holdCall: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.hold();
-        }
-    }
-
-    @Override
-    public void unholdCall(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "unholdCall: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.unhold();
-        }
-    }
-
-    @Override
-    public void playDtmfTone(UiCall uiCall, char digit) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "playDtmfTone: call: " + uiCall + ", digit: " + digit);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.playDtmfTone(digit);
-        }
-    }
-
-    @Override
-    public void stopDtmfTone(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "stopDtmfTone: call: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.stopDtmfTone();
-        }
-    }
-
-    @Override
-    public void postDialContinue(UiCall uiCall, boolean proceed) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "postDialContinue: call: " + uiCall + ", proceed: " + proceed);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.postDialContinue(proceed);
-        }
-    }
-
-    @Override
-    public void conference(UiCall uiCall, UiCall otherUiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "conference: call: " + uiCall + ", otherCall: " + otherUiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        Call otherTelecomCall = mCallMapping.get(otherUiCall);
-        if (telecomCall != null) {
-            telecomCall.conference(otherTelecomCall);
-        }
-    }
-
-    @Override
-    public void splitFromConference(UiCall uiCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "splitFromConference: call: " + uiCall);
-        }
-
-        Call telecomCall = mCallMapping.get(uiCall);
-        if (telecomCall != null) {
-            telecomCall.splitFromConference();
-        }
-    }
-
-    private UiCall doTelecomCallAdded(final Call telecomCall) {
-        Log.d(TAG, "doTelecomCallAdded: " + telecomCall);
-
-        UiCall uiCall = getOrCreateCallContainer(telecomCall);
-        telecomCall.registerCallback(new TelecomCallListener(this, uiCall));
-        for (CallListener listener : mCallListeners) {
-            listener.onCallAdded(uiCall);
-        }
-        Log.d(TAG, "Call backs registered");
-
-        if (telecomCall.getState() == Call.STATE_SELECT_PHONE_ACCOUNT) {
-            // TODO(b/26189994): need to show Phone Account picker to let user choose a phone
-            // account. It should be an account from TelecomManager#getCallCapablePhoneAccounts
-            // list.
-            Log.w(TAG, "Need to select phone account for the given call: " + telecomCall + ", "
-                    + "but this feature is not implemented yet.");
-            telecomCall.disconnect();
-        }
-        return uiCall;
-    }
-
-    private void doTelecomCallRemoved(Call telecomCall) {
-        UiCall uiCall = getOrCreateCallContainer(telecomCall);
-
-        mCallMapping.remove(uiCall);
-
-        for (CallListener listener : mCallListeners) {
-            listener.onCallRemoved(uiCall);
-        }
-    }
-
-    private void doCallAudioStateChanged(CallAudioState audioState) {
-        for (CallListener listener : mCallListeners) {
-            listener.onAudioStateChanged(audioState.isMuted(), audioState.getRoute(),
-                    audioState.getSupportedRouteMask());
-        }
-    }
-
-    private void onStateChanged(UiCall uiCall, int state) {
-        for (CallListener listener : mCallListeners) {
-            listener.onStateChanged(uiCall, state);
-        }
-    }
-
-    private void onCallUpdated(UiCall uiCall) {
-        for (CallListener listener : mCallListeners) {
-            listener.onCallUpdated(uiCall);
-        }
-    }
-
-    private static class TelecomCallListener extends Call.Callback {
-        private final WeakReference<TelecomUiCallManager> mCarTelecomMangerRef;
-        private final WeakReference<UiCall> mCallContainerRef;
-
-        TelecomCallListener(TelecomUiCallManager carTelecomManager, UiCall uiCall) {
-            mCarTelecomMangerRef = new WeakReference<>(carTelecomManager);
-            mCallContainerRef = new WeakReference<>(uiCall);
-        }
-
-        @Override
-        public void onStateChanged(Call telecomCall, int state) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onStateChanged: " + state);
-            }
-            TelecomUiCallManager manager = mCarTelecomMangerRef.get();
-            UiCall call = mCallContainerRef.get();
-            if (manager != null && call != null) {
-                call.setState(state);
-                manager.onStateChanged(call, state);
-            }
-        }
-
-        @Override
-        public void onParentChanged(Call telecomCall, Call parent) {
-            doCallUpdated(telecomCall);
-        }
-
-        @Override
-        public void onCallDestroyed(Call telecomCall) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onCallDestroyed");
-            }
-        }
-
-        @Override
-        public void onDetailsChanged(Call telecomCall, Details details) {
-            doCallUpdated(telecomCall);
-        }
-
-        @Override
-        public void onVideoCallChanged(Call telecomCall, VideoCall videoCall) {
-            doCallUpdated(telecomCall);
-        }
-
-        @Override
-        public void onCannedTextResponsesLoaded(Call telecomCall,
-                List<String> cannedTextResponses) {
-            doCallUpdated(telecomCall);
-        }
-
-        @Override
-        public void onChildrenChanged(Call telecomCall, List<Call> children) {
-            doCallUpdated(telecomCall);
-        }
-
-        private void doCallUpdated(Call telecomCall) {
-            TelecomUiCallManager manager = mCarTelecomMangerRef.get();
-            UiCall uiCall = mCallContainerRef.get();
-            if (manager != null && uiCall != null) {
-                updateCallContainerFromTelecom(uiCall, telecomCall);
-                manager.onCallUpdated(uiCall);
-            }
-        }
-    }
-
-    private ServiceConnection mInCallServiceConnection = new ServiceConnection() {
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder binder) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onServiceConnected: " + name + ", service: " + binder);
-            }
-            mInCallService = ((InCallServiceImpl.LocalBinder) binder).getService();
-            mInCallService.registerCallback(mInCallServiceCallback);
-
-            // The InCallServiceImpl could be bound when we already have some active calls, let's
-            // notify UI about these calls.
-            for (Call telecomCall : mInCallService.getCalls()) {
-                UiCall uiCall = doTelecomCallAdded(telecomCall);
-                onStateChanged(uiCall, uiCall.getState());
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onServiceDisconnected: " + name);
-            }
-            mInCallService.unregisterCallback(mInCallServiceCallback);
-        }
-
-        private InCallServiceImpl.Callback mInCallServiceCallback =
-                new InCallServiceImpl.Callback() {
-
-            @Override
-            public void onTelecomCallAdded(Call telecomCall) {
-                doTelecomCallAdded(telecomCall);
-            }
-
-            @Override
-            public void onTelecomCallRemoved(Call telecomCall) {
-                doTelecomCallRemoved(telecomCall);
-            }
-
-            @Override
-            public void onCallAudioStateChanged(CallAudioState audioState) {
-                doCallAudioStateChanged(audioState);
-            }
-        };
-    };
-
-    private UiCall getOrCreateCallContainer(Call telecomCall) {
-        for (Map.Entry<UiCall, Call> entry : mCallMapping.entrySet()) {
-            if (entry.getValue() == telecomCall) {
-                return entry.getKey();
-            }
-        }
-
-        UiCall uiCall = new UiCall(nextCarPhoneCallId++);
-        updateCallContainerFromTelecom(uiCall, telecomCall);
-        mCallMapping.put(uiCall, telecomCall);
-        return uiCall;
-    }
-
-    private static void updateCallContainerFromTelecom(UiCall uiCall, Call telecomCall) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "updateCallContainerFromTelecom: call: " + uiCall + ", telecomCall: "
-                    + telecomCall);
-        }
-
-        uiCall.setState(telecomCall.getState());
-        uiCall.setHasChildren(!telecomCall.getChildren().isEmpty());
-        uiCall.setHasParent(telecomCall.getParent() != null);
-
-        Call.Details details = telecomCall.getDetails();
-        if (details == null) {
-            return;
-        }
-
-        uiCall.setConnectTimeMillis(details.getConnectTimeMillis());
-
-        DisconnectCause cause = details.getDisconnectCause();
-        uiCall.setDisconnectCause(cause == null ? null : cause.getLabel());
-
-        GatewayInfo gatewayInfo = details.getGatewayInfo();
-        uiCall.setGatewayInfoOriginalAddress(
-                gatewayInfo == null ? null : gatewayInfo.getOriginalAddress());
-
-        String number = "";
-        if (gatewayInfo != null) {
-            number = gatewayInfo.getOriginalAddress().getSchemeSpecificPart();
-        } else if (details.getHandle() != null) {
-            number = details.getHandle().getSchemeSpecificPart();
-        }
-        uiCall.setNumber(number);
-
-    }
-
-    private CallAudioState getCallAudioStateOrNull() {
-        return mInCallService != null ? mInCallService.getCallAudioState() : null;
-    }
-}