Clean up Dialer code.

- Clean up the use of fragments. Create newInstance() methods that will
set the appropriate variables via arguments.
- Ensure that TelecomActivity does not hold onto Fragment instances as
this could lead to wrong lifecycle events being triggered.
- Restructure OngoingCallFragment so that methods are more concise
- Switch to using onStop/onStart in TelecomActivity to handle
multi-window.
- Remove the usage of commitAllowingStateLoss. Use commitNow/commit
where appropriate.
- Properly hook up the tone sounds for dialing
- Remove unused code

Test: connected phone via bluetooth, made some calls, dialed number,
ensure no crashes
Change-Id: Ib70367a970b09abb1fcb2621f03f940c31e92468
diff --git a/src/com/android/car/dialer/ContactDetailsFragment.java b/src/com/android/car/dialer/ContactDetailsFragment.java
index a334f72..9feb00f 100644
--- a/src/com/android/car/dialer/ContactDetailsFragment.java
+++ b/src/com/android/car/dialer/ContactDetailsFragment.java
@@ -48,8 +48,8 @@
 public class ContactDetailsFragment extends Fragment
         implements LoaderManager.LoaderCallbacks<Cursor> {
     private static final String TAG = "ContactDetailsFragment";
-    private static final int DETAILS_QUERY_ID = 31415;
-    private static final int PHONE_QUERY_ID = 42;
+    private static final int DETAILS_LOADER_QUERY_ID = 1;
+    private static final int PHONE_LOADER_QUERY_ID = 2;
     private static final String KEY_URI = "uri";
 
     private static final String[] CONTACT_DETAILS_PROJECTION = {
@@ -64,10 +64,12 @@
 
     public static ContactDetailsFragment newInstance(Uri uri, UiCallManager callManager) {
         ContactDetailsFragment fragment = new ContactDetailsFragment();
+        fragment.mCallManager = callManager;
+
         Bundle args = new Bundle();
         args.putParcelable(KEY_URI, uri);
         fragment.setArguments(args);
-        fragment.mCallManager = callManager;
+
         return fragment;
     }
 
@@ -82,7 +84,7 @@
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
-        getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
+        getLoaderManager().initLoader(DETAILS_LOADER_QUERY_ID, null, this);
     }
 
     @Override
@@ -91,7 +93,7 @@
             Log.d(TAG, "onCreateLoader id=" + id);
         }
 
-        if (id != DETAILS_QUERY_ID) {
+        if (id != DETAILS_LOADER_QUERY_ID) {
             return null;
         }
 
@@ -125,8 +127,6 @@
         public TextView text;
         public ImageView rightIcon;
 
-        private int mViewType;
-
         public ContactDetailViewHolder(View v) {
             super(v);
             card = v.findViewById(R.id.card);
@@ -143,24 +143,17 @@
         private static final int ID_HEADER = 1;
         private static final int ID_CONTENT = 2;
 
-        private final List<ContactDetailViewHolder> mData = new ArrayList<>();
-        private final Cursor mCursor;
-
         private final String mContactName;
-        private final String mContactPhotoUri;
 
         private List<Pair<String, String>> mPhoneNumbers = new ArrayList<>();
 
         public ContactDetailsAdapter(Cursor cursor) {
             super();
-            mCursor = cursor;
 
             int idColIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID);
             String contactId = cursor.getString(idColIdx);
             int nameColIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
             mContactName = cursor.getString(nameColIdx);
-            int photoColIdx = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI);
-            mContactPhotoUri = cursor.getString(photoColIdx);
             int hasPhoneColIdx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
             boolean hasPhoneNumber = Integer.parseInt(cursor.getString(hasPhoneColIdx)) > 0;
 
@@ -169,7 +162,7 @@
             }
 
             // Fetch the phone number from the contacts db using another loader.
-            getLoaderManager().initLoader(PHONE_QUERY_ID, null,
+            getLoaderManager().initLoader(PHONE_LOADER_QUERY_ID, null,
                     new LoaderManager.LoaderCallbacks<Cursor>() {
                 @Override
                 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
diff --git a/src/com/android/car/dialer/DialerFragment.java b/src/com/android/car/dialer/DialerFragment.java
index bf3197a..642bf19 100644
--- a/src/com/android/car/dialer/DialerFragment.java
+++ b/src/com/android/car/dialer/DialerFragment.java
@@ -19,16 +19,18 @@
 import android.media.AudioManager;
 import android.media.ToneGenerator;
 import android.os.Bundle;
-import android.os.Handler;
+import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
+
 import com.android.car.apps.common.FabDrawable;
 import com.android.car.dialer.telecom.TelecomUtils;
 import com.android.car.dialer.telecom.UiCallManager;
@@ -40,12 +42,14 @@
 public class DialerFragment extends Fragment {
     private static final String TAG = "Em.DialerFragment";
     private static final String INPUT_ACTIVE_KEY = "INPUT_ACTIVE_KEY";
-    private static final int TONE_RELATIVE_VOLUME = 80;
-    private static final int ABANDON_AUDIO_FOCUS_DELAY_MS = 250;
-    private static final int MAX_DIAL_NUMBER = 20;
-    private static final int NO_TONE = -1;
+    private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY";
 
-    private static final ArrayMap<Integer, Integer> mToneMap = new ArrayMap<>();
+    private static final int TONE_LENGTH_MS = 150;
+    private static final int TONE_RELATIVE_VOLUME = 80;
+    private static final int MAX_DIAL_NUMBER = 20;
+
+    private static final SparseIntArray mToneMap = new SparseIntArray();
+    private static final SparseArray<String> mDialValueMap = new SparseArray<>();
 
     static {
         mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1);
@@ -60,14 +64,25 @@
         mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0);
         mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S);
         mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P);
+
+        mDialValueMap.put(KeyEvent.KEYCODE_1, "1");
+        mDialValueMap.put(KeyEvent.KEYCODE_2, "2");
+        mDialValueMap.put(KeyEvent.KEYCODE_3, "3");
+        mDialValueMap.put(KeyEvent.KEYCODE_4, "4");
+        mDialValueMap.put(KeyEvent.KEYCODE_5, "5");
+        mDialValueMap.put(KeyEvent.KEYCODE_6, "6");
+        mDialValueMap.put(KeyEvent.KEYCODE_7, "7");
+        mDialValueMap.put(KeyEvent.KEYCODE_8, "8");
+        mDialValueMap.put(KeyEvent.KEYCODE_9, "9");
+        mDialValueMap.put(KeyEvent.KEYCODE_0, "0");
+        mDialValueMap.put(KeyEvent.KEYCODE_STAR, "*");
+        mDialValueMap.put(KeyEvent.KEYCODE_POUND, "#");
     }
 
     private Context mContext;
     private UiCallManager mUiCallManager;
     private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
-    private AudioManager mAudioManager;
     private ToneGenerator mToneGenerator;
-    private final Handler mHandler = new Handler();
     private final Object mToneGeneratorLock = new Object();
     private TextView mNumberView;
     private boolean mShowInput = true;
@@ -87,28 +102,36 @@
         void onDialerBackClick();
     }
 
-    public static DialerFragment newInstance(UiCallManager callManager) {
+    /**
+     * Creates a new instance of the {@link DialerFragment} and display the given number as the one
+     * to dial.
+     */
+    static DialerFragment newInstance(UiCallManager callManager,
+            DialerBackButtonListener listener, @Nullable String dialNumber) {
         DialerFragment fragment = new DialerFragment();
         fragment.mUiCallManager = callManager;
-        return fragment;
-    }
+        fragment.mBackListener = listener;
 
-    /**
-     * 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.
-     */
-    public void setDialerBackButtonListener(DialerBackButtonListener listener) {
-        mBackListener = listener;
+        if (!TextUtils.isEmpty(dialNumber)) {
+            Bundle args = new Bundle();
+            args.putString(DIAL_NUMBER_KEY, dialNumber);
+            fragment.setArguments(args);
+        }
+
+        return fragment;
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mAudioManager =
-                (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
         if (savedInstanceState != null && savedInstanceState.containsKey(INPUT_ACTIVE_KEY)) {
             mShowInput = savedInstanceState.getBoolean(INPUT_ACTIVE_KEY);
         }
+
+        Bundle args = getArguments();
+        if (args != null) {
+            setDialNumber(args.getString(DIAL_NUMBER_KEY));
+        }
     }
 
     @Override
@@ -125,7 +148,7 @@
             Log.v(TAG, "onCreateView: inflated successfully");
         }
 
-        view.findViewById(R.id.exit_dialer_button).setOnClickListener((unusedView) -> {
+        view.findViewById(R.id.exit_dialer_button).setOnClickListener(v -> {
             if (mBackListener != null) {
                 mBackListener.onDialerBackClick();
             }
@@ -137,9 +160,10 @@
             Log.v(TAG, "mShowInput: " + mShowInput);
         }
 
-        View callButton = view.findViewById(R.id.call);
         FabDrawable answerCallDrawable = new FabDrawable(mContext);
-        answerCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
+        answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call));
+
+        View callButton = view.findViewById(R.id.call);
         callButton.setBackground(answerCallDrawable);
         callButton.setVisibility(View.VISIBLE);
         callButton.setOnClickListener((unusedView) -> {
@@ -151,16 +175,17 @@
                 mUiCallManager.safePlaceCall(mNumber.toString(), false);
             }
         });
+
         View deleteButton = view.findViewById(R.id.delete);
         deleteButton.setVisibility(View.VISIBLE);
-        deleteButton.setOnClickListener((unusedView) -> {
+        deleteButton.setOnClickListener(v -> {
             if (mNumber.length() != 0) {
                 mNumber.deleteCharAt(mNumber.length() - 1);
                 mNumberView.setText(getFormattedNumber(mNumber.toString()));
             }
         });
 
-        setupKeypad(view);
+        setupKeypadClickListeners(view);
 
         return view;
     }
@@ -170,35 +195,47 @@
      * associated value to {@link #mNumber}.
      */
     private class DialpadClickListener implements View.OnClickListener {
-        private String mValue;
+        private final int mTone;
+        private final String mValue;
 
-        public DialpadClickListener(String value) {
-            mValue = value;
+        DialpadClickListener(int keyCode) {
+            mTone = mToneMap.get(keyCode);
+            mValue = mDialValueMap.get(keyCode);
         }
 
         @Override
         public void onClick(View v) {
             mNumber.append(mValue);
             mNumberView.setText(getFormattedNumber(mNumber.toString()));
+            playTone(mTone);
         }
-    };
+    }
 
-    /**
-     * Sets up the click listeners for all the dialpad buttons.
-     */
-    private void setupKeypad(View parent) {
-        parent.findViewById(R.id.zero).setOnClickListener(new DialpadClickListener("0"));
-        parent.findViewById(R.id.one).setOnClickListener(new DialpadClickListener("1"));
-        parent.findViewById(R.id.two).setOnClickListener(new DialpadClickListener("2"));
-        parent.findViewById(R.id.three).setOnClickListener(new DialpadClickListener("3"));
-        parent.findViewById(R.id.four).setOnClickListener(new DialpadClickListener("4"));
-        parent.findViewById(R.id.five).setOnClickListener(new DialpadClickListener("5"));
-        parent.findViewById(R.id.six).setOnClickListener(new DialpadClickListener("6"));
-        parent.findViewById(R.id.seven).setOnClickListener(new DialpadClickListener("7"));
-        parent.findViewById(R.id.eight).setOnClickListener(new DialpadClickListener("8"));
-        parent.findViewById(R.id.nine).setOnClickListener(new DialpadClickListener("9"));
-        parent.findViewById(R.id.star).setOnClickListener(new DialpadClickListener("*"));
-        parent.findViewById(R.id.pound).setOnClickListener(new DialpadClickListener("#"));
+    private void setupKeypadClickListeners(View parent) {
+        parent.findViewById(R.id.zero).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_0));
+        parent.findViewById(R.id.one).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_1));
+        parent.findViewById(R.id.two).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_2));
+        parent.findViewById(R.id.three).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_3));
+        parent.findViewById(R.id.four).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_4));
+        parent.findViewById(R.id.five).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_5));
+        parent.findViewById(R.id.six).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_6));
+        parent.findViewById(R.id.seven).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_7));
+        parent.findViewById(R.id.eight).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_8));
+        parent.findViewById(R.id.nine).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_9));
+        parent.findViewById(R.id.star).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_STAR));
+        parent.findViewById(R.id.pound).setOnClickListener(
+                new DialpadClickListener(KeyEvent.KEYCODE_POUND));
     }
 
     @Override
@@ -237,7 +274,7 @@
         mNumberView = null;
     }
 
-    public void setDialNumber(final String number) {
+    private void setDialNumber(final String number) {
         if (TextUtils.isEmpty(number)) {
             return;
         }
@@ -256,6 +293,18 @@
         mNumberView.setText(getFormattedNumber(mNumber.toString()));
     }
 
+    private void playTone(int tone) {
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
+                return;
+            }
+
+            // Start the new tone (will stop any playing tone)
+            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+        }
+    }
+
     private void stopTone() {
         synchronized (mToneGeneratorLock) {
             if (mToneGenerator == null) {
@@ -263,17 +312,9 @@
                 return;
             }
             mToneGenerator.stopTone();
-            mHandler.postDelayed(mDelayedAbandonAudioFocusRunnable, ABANDON_AUDIO_FOCUS_DELAY_MS);
         }
     }
 
-    private final Runnable mDelayedAbandonAudioFocusRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mAudioManager.abandonAudioFocus(null);
-        }
-    };
-
     private String getFormattedNumber(String number) {
         return TelecomUtils.getFormattedNumber(mContext, number);
     }
diff --git a/src/com/android/car/dialer/DialpadButton.java b/src/com/android/car/dialer/DialpadButton.java
index 28926f5..7e058c0 100644
--- a/src/com/android/car/dialer/DialpadButton.java
+++ b/src/com/android/car/dialer/DialpadButton.java
@@ -31,7 +31,7 @@
 
     private String mNumberText;
     private String mLetterText;
-    private int mImageRes;
+    private int mImageRes = INVALID_IMAGE_RES;
 
     public DialpadButton(Context context) {
         super(context);
diff --git a/src/com/android/car/dialer/NoHfpFragment.java b/src/com/android/car/dialer/NoHfpFragment.java
index 16e736a..295b474 100644
--- a/src/com/android/car/dialer/NoHfpFragment.java
+++ b/src/com/android/car/dialer/NoHfpFragment.java
@@ -23,22 +23,63 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-
+/**
+ * A fragment that informs the user that there is no bluetooth device attached that can make
+ * phone calls.
+ */
 public class NoHfpFragment extends Fragment {
+    private static final String ERROR_MESSAGE_KEY = "ERROR_MESSAGE_KEY";
+
+    private TextView mErrorMessageView;
     private String mErrorMessage;
 
-    public void setErrorMessage(String message) {
-        mErrorMessage = message;
+    /**
+     * Returns an instance of the {@link NoHfpFragment} with the given error message as the one to
+     * display.
+     */
+    static NoHfpFragment newInstance(String errorMessage) {
+        NoHfpFragment fragment = new NoHfpFragment();
+
+        Bundle args = new Bundle();
+        args.putString(ERROR_MESSAGE_KEY, errorMessage);
+        fragment.setArguments(args);
+
+        return fragment;
+    }
+
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Bundle args = getArguments();
+        if (args != null) {
+            mErrorMessage = args.getString(ERROR_MESSAGE_KEY);
+        }
+    }
+
+    /**
+     * Sets the given error message to be displayed.
+     */
+    void setErrorMessage(String errorMessage) {
+        mErrorMessage = errorMessage;
+
+        // If this method is called before the error message view is available, then no need to
+        // set the message. Instead, it will be set in onCreateView().
+        if (mErrorMessageView != null && !TextUtils.isEmpty(mErrorMessage)) {
+            mErrorMessageView.setText(mErrorMessage);
+        }
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.no_hfp, container, false);
+        mErrorMessageView = v.findViewById(R.id.error_string);
+
         // If no error message is set, the default string from the layout will be used.
         if (!TextUtils.isEmpty(mErrorMessage)) {
-            ((TextView) v.findViewById(R.id.error_string)).setText(mErrorMessage);
+            mErrorMessageView.setText(mErrorMessage);
         }
+
         return v;
     }
 }
diff --git a/src/com/android/car/dialer/OngoingCallFragment.java b/src/com/android/car/dialer/OngoingCallFragment.java
index 7e88f7b..87d4ebb 100644
--- a/src/com/android/car/dialer/OngoingCallFragment.java
+++ b/src/com/android/car/dialer/OngoingCallFragment.java
@@ -27,20 +27,20 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
-import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+
 import com.android.car.apps.common.CircleBitmapDrawable;
 import com.android.car.apps.common.FabDrawable;
 import com.android.car.dialer.telecom.TelecomUtils;
@@ -49,12 +49,15 @@
 import com.android.car.dialer.telecom.UiCallManager.CallListener;
 
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 
+/**
+ * A fragment that displays information about an on-going call with options to hang up.
+ */
 public class OngoingCallFragment extends Fragment {
-    private static final String TAG = "Em.OngoingCall";
-    private static final HashMap<Integer, Character> mDialpadButtonMap = new HashMap<>();
+    private static final String TAG = "OngoingCall";
+    private static final SparseArray<Character> mDialpadButtonMap = new SparseArray<>();
 
     static {
         mDialpadButtonMap.put(R.id.one, '1');
@@ -71,11 +74,10 @@
         mDialpadButtonMap.put(R.id.pound, '#');
     }
 
-    private UiCall mPrimaryCall;
-    private UiCall mSecondaryCall;
+    private final Handler mHandler = new Handler();
+
     private UiCall mLastRemovedCall;
     private UiCallManager mUiCallManager;
-    private Handler mHandler;
     private View mRingingCallControls;
     private View mActiveCallControls;
     private ImageButton mEndCallButton;
@@ -95,17 +97,12 @@
     private View mDialpadContainer;
     private View mSecondaryCallContainer;
     private View mSecondaryCallControls;
-    private List<View> mDialpadViews;
     private String mLoadedNumber;
     private CharSequence mCallInfoLabel;
     private UiBluetoothMonitor mUiBluetoothMonitor;
 
-    private final Interpolator
-            mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
-    private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator(10);
-
-    public static OngoingCallFragment newInstance(
-            UiCallManager callManager, UiBluetoothMonitor btMonitor) {
+    static OngoingCallFragment newInstance(UiCallManager callManager,
+            UiBluetoothMonitor btMonitor) {
         OngoingCallFragment fragment = new OngoingCallFragment();
         fragment.mUiCallManager = callManager;
         fragment.mUiBluetoothMonitor = btMonitor;
@@ -115,7 +112,6 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mHandler = new Handler();
     }
 
     @Override
@@ -123,35 +119,17 @@
         super.onDestroy();
         mHandler.removeCallbacks(mUpdateDurationRunnable);
         mHandler.removeCallbacks(mStopDtmfToneRunnable);
-        mHandler = null;
         mLoadedNumber = null;
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
+            Bundle savedInstanceState) {
         View view = inflater.inflate(R.layout.ongoing_call, container, false);
-        mRingingCallControls = view.findViewById(R.id.ringing_call_controls);
-        mActiveCallControls = view.findViewById(R.id.active_call_controls);
-        mEndCallButton = (ImageButton) view.findViewById(R.id.end_call);
-        mUnholdCallButton = (ImageButton) view.findViewById(R.id.unhold_call);
-        mMuteButton = (ImageButton) view.findViewById(R.id.mute);
-        mToggleDialpadButton = (ImageButton) view.findViewById(R.id.toggle_dialpad);
-        mDialpadContainer = view.findViewById(R.id.dialpad_container);
-        mNameTextView = (TextView) view.findViewById(R.id.name);
-        mSecondaryNameTextView = (TextView) view.findViewById(R.id.name_secondary);
-        mStateTextView = (TextView) view.findViewById(R.id.info);
-        mSecondaryStateTextView = (TextView) view.findViewById(R.id.info_secondary);
-        mLargeContactPhotoView = (ImageView) view.findViewById(R.id.large_contact_photo);
-        mSmallContactPhotoView = (ImageView) view.findViewById(R.id.small_contact_photo);
-        mSecondaryCallContainer = view.findViewById(R.id.secondary_call_container);
-        mSecondaryCallControls = view.findViewById(R.id.secondary_call_controls);
-        mSwapButton = (ImageButton) view.findViewById(R.id.swap);
-        mMergeButton = (ImageButton) view.findViewById(R.id.merge);
-        mAnswerCallButton = (ImageButton) view.findViewById(R.id.answer_call_button);
-        mRejectCallButton = (ImageButton) view.findViewById(R.id.reject_call_button);
+        initializeViews(view);
+        initializeClickListeners();
 
-        mDialpadViews = Arrays.asList(
+        List<View> dialpadViews = Arrays.asList(
                 mDialpadContainer.findViewById(R.id.one),
                 mDialpadContainer.findViewById(R.id.two),
                 mDialpadContainer.findViewById(R.id.three),
@@ -163,16 +141,58 @@
                 mDialpadContainer.findViewById(R.id.nine),
                 mDialpadContainer.findViewById(R.id.zero),
                 mDialpadContainer.findViewById(R.id.pound),
-                mDialpadContainer.findViewById(R.id.star)
-        );
+                mDialpadContainer.findViewById(R.id.star));
 
         // In touch screen, we need to adjust the InCall card for the narrow screen to show the
         // full dial pad.
-        for (View dialpadView : mDialpadViews) {
+        for (View dialpadView : dialpadViews) {
             dialpadView.setOnTouchListener(mDialpadTouchListener);
             dialpadView.setOnKeyListener(mDialpadKeyListener);
         }
 
+        mUiCallManager.addListener(mCallListener);
+
+        updateCalls();
+
+        return view;
+    }
+
+    private void initializeViews(View parent) {
+        mRingingCallControls = parent.findViewById(R.id.ringing_call_controls);
+        mActiveCallControls = parent.findViewById(R.id.active_call_controls);
+        mEndCallButton = parent.findViewById(R.id.end_call);
+        mUnholdCallButton = parent.findViewById(R.id.unhold_call);
+        mMuteButton = parent.findViewById(R.id.mute);
+        mToggleDialpadButton = parent.findViewById(R.id.toggle_dialpad);
+        mDialpadContainer = parent.findViewById(R.id.dialpad_container);
+        mNameTextView = parent.findViewById(R.id.name);
+        mSecondaryNameTextView = parent.findViewById(R.id.name_secondary);
+        mStateTextView = parent.findViewById(R.id.info);
+        mSecondaryStateTextView = parent.findViewById(R.id.info_secondary);
+        mLargeContactPhotoView = parent.findViewById(R.id.large_contact_photo);
+        mSmallContactPhotoView = parent.findViewById(R.id.small_contact_photo);
+        mSecondaryCallContainer = parent.findViewById(R.id.secondary_call_container);
+        mSecondaryCallControls = parent.findViewById(R.id.secondary_call_controls);
+        mSwapButton = parent.findViewById(R.id.swap);
+        mMergeButton = parent.findViewById(R.id.merge);
+        mAnswerCallButton = parent.findViewById(R.id.answer_call_button);
+        mRejectCallButton = parent.findViewById(R.id.reject_call_button);
+
+        Context context = getContext();
+        FabDrawable drawable = new FabDrawable(context);
+        drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call));
+        mAnswerCallButton.setBackground(drawable);
+
+        drawable = new FabDrawable(context);
+        drawable.setFabAndStrokeColor(context.getColor(R.color.phone_end_call));
+        mEndCallButton.setBackground(drawable);
+
+        drawable = new FabDrawable(context);
+        drawable.setFabAndStrokeColor(context.getColor(R.color.phone_call));
+        mUnholdCallButton.setBackground(drawable);
+    }
+
+    private void initializeClickListeners() {
         mAnswerCallButton.setOnClickListener((unusedView) -> {
             UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
             if (call == null) {
@@ -181,10 +201,6 @@
             }
             mUiCallManager.answerCall(call);
         });
-        Context context = getContext();
-        FabDrawable answerCallDrawable = new FabDrawable(context);
-        answerCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
-        mAnswerCallButton.setBackground(answerCallDrawable);
 
         mRejectCallButton.setOnClickListener((unusedView) -> {
             UiCall call = mUiCallManager.getCallWithState(Call.STATE_RINGING);
@@ -203,9 +219,6 @@
             }
             mUiCallManager.disconnectCall(call);
         });
-        FabDrawable endCallDrawable = new FabDrawable(context);
-        endCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_end_call));
-        mEndCallButton.setBackground(endCallDrawable);
 
         mUnholdCallButton.setOnClickListener((unusedView) -> {
             UiCall call = mUiCallManager.getPrimaryCall();
@@ -215,17 +228,9 @@
             }
             mUiCallManager.unholdCall(call);
         });
-        FabDrawable unholdCallDrawable = new FabDrawable(context);
-        unholdCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
-        mUnholdCallButton.setBackground(unholdCallDrawable);
 
-        mMuteButton.setOnClickListener((unusedView) -> {
-            if (mUiCallManager.getMuted()) {
-                mUiCallManager.setMuted(false);
-            } else {
-                mUiCallManager.setMuted(true);
-            }
-        });
+        mMuteButton.setOnClickListener(
+                (unusedView) -> mUiCallManager.setMuted(!mUiCallManager.getMuted()));
 
         mSwapButton.setOnClickListener((unusedView) -> {
             UiCall call = mUiCallManager.getPrimaryCall();
@@ -242,13 +247,13 @@
 
         mMergeButton.setOnClickListener((unusedView) -> {
             UiCall call = mUiCallManager.getPrimaryCall();
-            UiCall secondarycall = mUiCallManager.getSecondaryCall();
-            if (call == null || mSecondaryCall == null) {
+            UiCall secondaryCall = mUiCallManager.getSecondaryCall();
+            if (call == null || secondaryCall == null) {
                 Log.w(TAG, "There aren't two call to merge.");
                 return;
             }
 
-            mUiCallManager.conference(call, secondarycall);
+            mUiCallManager.conference(call, secondaryCall);
         });
 
         mToggleDialpadButton.setOnClickListener((unusedView) -> {
@@ -258,12 +263,6 @@
                 openDialpad(true /*animate*/);
             }
         });
-
-        mUiCallManager.addListener(mCallListener);
-
-        updateCalls();
-
-        return view;
     }
 
     @Override
@@ -278,24 +277,32 @@
         trySpeakerAudioRouteIfNecessary();
     }
 
-    private void rebindViews() {
+    private void updateCalls() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "updateCalls(); Primary call: " + mUiCallManager.getPrimaryCall()
+                    + "; Secondary call:" + mUiCallManager.getSecondaryCall());
+        }
+
         mHandler.removeCallbacks(mUpdateDurationRunnable);
 
-        // Toggle the visibility between the active call controls, ringing call controls,
-        // and no controls.
-        CharSequence disconnectCauseLabel = mLastRemovedCall == null ?
-                null : mLastRemovedCall.getDisconnectCause();
-        if (mPrimaryCall == null && !TextUtils.isEmpty(disconnectCauseLabel)) {
+        UiCall primaryCall = mUiCallManager.getPrimaryCall();
+        CharSequence disconnectCauseLabel = mLastRemovedCall == null
+                ? null : mLastRemovedCall.getDisconnectCause();
+        if (primaryCall == null && !TextUtils.isEmpty(disconnectCauseLabel)) {
             closeDialpad();
             setStateText(disconnectCauseLabel);
             return;
-        } else if (mPrimaryCall == null || mPrimaryCall.getState() == Call.STATE_DISCONNECTED) {
+        }
+
+        if (primaryCall == null || primaryCall.getState() == Call.STATE_DISCONNECTED) {
             closeDialpad();
             setStateText(getString(R.string.call_state_call_ended));
             mRingingCallControls.setVisibility(View.GONE);
             mActiveCallControls.setVisibility(View.GONE);
             return;
-        } else if (mPrimaryCall.getState() == Call.STATE_RINGING) {
+        }
+
+        if (primaryCall.getState() == Call.STATE_RINGING) {
             mRingingCallControls.setVisibility(View.VISIBLE);
             mActiveCallControls.setVisibility(View.GONE);
         } else {
@@ -303,85 +310,19 @@
             mActiveCallControls.setVisibility(View.VISIBLE);
         }
 
-        // Show the primary contact photo in the large ImageView on the right if there is no
-        // secondary call. Otherwise, show it in the small ImageView that is inside the card.
-        Context context = getContext();
-        final ContentResolver cr = context.getContentResolver();
-        final String primaryNumber = mPrimaryCall.getNumber();
-        // Don't reload the image if the number is the same.
-        if ((primaryNumber != null && !primaryNumber.equals(mLoadedNumber))
-                || (primaryNumber == null && mLoadedNumber != null)) {
-            BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
-                @Override
-                public void run() {
-                    if (mBitmap != null) {
-                        Resources r = mSmallContactPhotoView.getResources();
-                        mSmallContactPhotoView.setImageDrawable(
-                                new CircleBitmapDrawable(r, mBitmap));
-                        mLargeContactPhotoView.setImageBitmap(mBitmap);
-                        mLargeContactPhotoView.clearColorFilter();
-                    } else {
-                        mSmallContactPhotoView.setImageResource(R.drawable.logo_avatar);
-                        mLargeContactPhotoView.setImageResource(R.drawable.ic_avatar_bg);
-                    }
+        loadContactPhotoForPrimaryNumber(primaryCall.getNumber());
 
-                    if (mSecondaryCall != null) {
-                        BitmapWorkerTask.BitmapRunnable secondCallContactPhotoHandler =
-                                new BitmapWorkerTask.BitmapRunnable() {
-                                    @Override
-                                    public void run() {
-                                        if (mBitmap != null) {
-                                            mLargeContactPhotoView.setImageBitmap(mBitmap);
-                                        } else {
-                                            mLargeContactPhotoView.setImageResource(
-                                                    R.drawable.logo_avatar);
-                                        }
-                                    }
-                                };
-
-                        BitmapWorkerTask.loadBitmap(
-                                cr, mLargeContactPhotoView, mSecondaryCall.getNumber(),
-                                secondCallContactPhotoHandler);
-
-                        int scrimColor = getResources().getColor(
-                                R.color.phone_secondary_call_scrim);
-                        mLargeContactPhotoView.setColorFilter(scrimColor);
-                    }
-                    mLoadedNumber = primaryNumber;
-                }
-            };
-            BitmapWorkerTask.loadBitmap(cr, mLargeContactPhotoView, primaryNumber, runnable);
-        }
-
-        if (mSecondaryCall != null) {
-            mSecondaryCallContainer.setVisibility(View.VISIBLE);
-            if (mPrimaryCall.getState() == Call.STATE_ACTIVE
-                    && mSecondaryCall.getState() == Call.STATE_HOLDING) {
-                mSecondaryCallControls.setVisibility(View.VISIBLE);
-            } else {
-                mSecondaryCallControls.setVisibility(View.GONE);
-            }
-        } else {
-            mSecondaryCallContainer.setVisibility(View.GONE);
-            mSecondaryCallControls.setVisibility(View.GONE);
-        }
-
-        String displayName = TelecomUtils.getDisplayName(context, mPrimaryCall);
+        String displayName = TelecomUtils.getDisplayName(getContext(), primaryCall);
         mNameTextView.setText(displayName);
         mNameTextView.setVisibility(TextUtils.isEmpty(displayName) ? View.GONE : View.VISIBLE);
 
-        if (mSecondaryCall != null) {
-            mSecondaryNameTextView.setText(
-                    TelecomUtils.getDisplayName(context, mSecondaryCall));
-        }
-
-        switch (mPrimaryCall.getState()) {
+        Context context = getContext();
+        switch (primaryCall.getState()) {
             case Call.STATE_NEW:
                 // Since the content resolver call is only cached when a contact is found,
                 // this should only be called once on a new call to avoid jank.
                 // TODO: consider moving TelecomUtils.getTypeFromNumber into a CursorLoader
-                String number = mPrimaryCall.getNumber();
-                mCallInfoLabel = TelecomUtils.getTypeFromNumber(context, number);
+                mCallInfoLabel = TelecomUtils.getTypeFromNumber(context, primaryCall.getNumber());
             case Call.STATE_CONNECTING:
             case Call.STATE_DIALING:
             case Call.STATE_SELECT_PHONE_ACCOUNT:
@@ -389,7 +330,7 @@
             case Call.STATE_DISCONNECTED:
                 mHandler.removeCallbacks(mUpdateDurationRunnable);
                 String callInfoText = TelecomUtils.getCallInfoText(context,
-                        mPrimaryCall, mCallInfoLabel);
+                        primaryCall, mCallInfoLabel);
                 setStateText(callInfoText);
                 break;
             case Call.STATE_ACTIVE:
@@ -401,17 +342,11 @@
                 Log.w(TAG, "There should not be a ringing call in the ongoing call fragment.");
                 break;
             default:
-                Log.w(TAG, "Unhandled call state: " + mPrimaryCall.getState());
-        }
-
-        if (mSecondaryCall != null) {
-            mSecondaryStateTextView.setText(
-                    TelecomUtils.callStateToUiString(context, mSecondaryCall.getState()));
+                Log.w(TAG, "Unhandled call state: " + primaryCall.getState());
         }
 
         // If it is a voicemail call, open the dialpad (with no animation).
-        if (primaryNumber != null && primaryNumber.equals(
-                TelecomUtils.getVoicemailNumber(context))) {
+        if (Objects.equals(primaryCall.getNumber(), TelecomUtils.getVoicemailNumber(context))) {
             openDialpad(false /*animate*/);
             mToggleDialpadButton.setVisibility(View.GONE);
         } else {
@@ -419,7 +354,7 @@
         }
 
         // Handle the holding case.
-        if (mPrimaryCall.getState() == Call.STATE_HOLDING) {
+        if (primaryCall.getState() == Call.STATE_HOLDING) {
             mEndCallButton.setVisibility(View.GONE);
             mUnholdCallButton.setVisibility(View.VISIBLE);
             mMuteButton.setVisibility(View.INVISIBLE);
@@ -430,6 +365,85 @@
             mMuteButton.setVisibility(View.VISIBLE);
             mToggleDialpadButton.setVisibility(View.VISIBLE);
         }
+
+        updateSecondaryCall(primaryCall, mUiCallManager.getSecondaryCall());
+    }
+
+    private void updateSecondaryCall(UiCall primaryCall, UiCall secondaryCall) {
+        if (primaryCall == null || secondaryCall == null) {
+            mSecondaryCallContainer.setVisibility(View.GONE);
+            mSecondaryCallControls.setVisibility(View.GONE);
+            return;
+        }
+
+        mSecondaryCallContainer.setVisibility(View.VISIBLE);
+
+        if (primaryCall.getState() == Call.STATE_ACTIVE
+                && secondaryCall.getState() == Call.STATE_HOLDING) {
+            mSecondaryCallControls.setVisibility(View.VISIBLE);
+        } else {
+            mSecondaryCallControls.setVisibility(View.GONE);
+        }
+
+        Context context = getContext();
+        mSecondaryNameTextView.setText(TelecomUtils.getDisplayName(context, secondaryCall));
+        mSecondaryStateTextView.setText(
+                TelecomUtils.callStateToUiString(context, secondaryCall.getState()));
+
+        loadContactPhotoForSecondaryNumber(secondaryCall.getNumber());
+    }
+
+    /**
+     * Loads the contact photo associated with the given number and sets it in the views that
+     * correspond with a primary number.
+     */
+    private void loadContactPhotoForPrimaryNumber(String primaryNumber) {
+        // Don't reload the image if the number is the same.
+        if (Objects.equals(primaryNumber, mLoadedNumber)) {
+            return;
+        }
+
+        final ContentResolver cr = getContext().getContentResolver();
+        BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
+            @Override
+            public void run() {
+                if (mBitmap != null) {
+                    Resources r = getResources();
+                    mSmallContactPhotoView.setImageDrawable(new CircleBitmapDrawable(r, mBitmap));
+                    mLargeContactPhotoView.setImageBitmap(mBitmap);
+                    mLargeContactPhotoView.clearColorFilter();
+                } else {
+                    mSmallContactPhotoView.setImageResource(R.drawable.logo_avatar);
+                    mLargeContactPhotoView.setImageResource(R.drawable.ic_avatar_bg);
+                }
+            }
+        };
+        mLoadedNumber = primaryNumber;
+        BitmapWorkerTask.loadBitmap(cr, mLargeContactPhotoView, primaryNumber, runnable);
+    }
+
+    /**
+     * Loads the contact photo associated with the given number and sets it in the views that
+     * correspond to a secondary number.
+     */
+    private void loadContactPhotoForSecondaryNumber(String secondaryNumber) {
+        BitmapWorkerTask.BitmapRunnable runnable = new BitmapWorkerTask.BitmapRunnable() {
+            @Override
+            public void run() {
+                if (mBitmap != null) {
+                    mLargeContactPhotoView.setImageBitmap(mBitmap);
+                } else {
+                    mLargeContactPhotoView.setImageResource(R.drawable.logo_avatar);
+                }
+            }
+        };
+
+        Context context = getContext();
+        BitmapWorkerTask.loadBitmap(context.getContentResolver(), mLargeContactPhotoView,
+                secondaryNumber, runnable);
+
+        int scrimColor = context.getColor(R.color.phone_secondary_call_scrim);
+        mLargeContactPhotoView.setColorFilter(scrimColor);
     }
 
     private void setStateText(CharSequence stateText) {
@@ -437,25 +451,14 @@
         mStateTextView.setVisibility(TextUtils.isEmpty(stateText) ? View.GONE : View.VISIBLE);
     }
 
-    private void updateCalls() {
-        mPrimaryCall = mUiCallManager.getPrimaryCall();
-        if (mPrimaryCall != null && mPrimaryCall.getState() == Call.STATE_RINGING) {
-            // TODO: update when notifications will work
-        }
-        mSecondaryCall = mUiCallManager.getSecondaryCall();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Primary call: " + mPrimaryCall + "\tSecondary call:" + mSecondaryCall);
-        }
-        rebindViews();
-    }
-
     /**
-     * If the phone is using bluetooth:
-     *     * Do nothing
-     * If the phone is not using bluetooth:
-     *     * If the phone supports bluetooth, use it.
-     *     * If the phone doesn't support bluetooth and support speaker, use speaker
-     *     * Otherwise, do nothing. Hopefully no phones won't have bt or speaker.
+     * If the phone is using bluetooth, then do nothing. If the phone is not using bluetooth:
+     * <p>
+     * <ol>
+     *     <li>If the phone supports bluetooth, use it.
+     *     <li>If the phone doesn't support bluetooth and support speaker, use speaker
+     *     <li>Otherwise, do nothing. Hopefully no phones won't have bt or speaker.
+     * </ol>
      */
     private void trySpeakerAudioRouteIfNecessary() {
         if (mUiCallManager == null) {
@@ -519,12 +522,12 @@
             }
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 v.setPressed(true);
-                mUiCallManager.playDtmfTone(mPrimaryCall, digit);
+                mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit);
                 return true;
             } else if (event.getAction() == MotionEvent.ACTION_UP) {
                 v.setPressed(false);
                 v.performClick();
-                mUiCallManager.stopDtmfTone(mPrimaryCall);
+                mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
                 return true;
             }
 
@@ -547,11 +550,11 @@
 
             if (event.getAction() == KeyEvent.ACTION_DOWN) {
                 v.setPressed(true);
-                mUiCallManager.playDtmfTone(mPrimaryCall, digit);
+                mUiCallManager.playDtmfTone(mUiCallManager.getPrimaryCall(), digit);
                 return true;
             } else if (event.getAction() == KeyEvent.ACTION_UP) {
                 v.setPressed(false);
-                mUiCallManager.stopDtmfTone(mPrimaryCall);
+                mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
                 return true;
             }
 
@@ -562,22 +565,20 @@
     private final Runnable mUpdateDurationRunnable = new Runnable() {
         @Override
         public void run() {
-            if (mPrimaryCall.getState() != Call.STATE_ACTIVE) {
+            UiCall primaryCall = mUiCallManager.getPrimaryCall();
+            if (primaryCall.getState() != Call.STATE_ACTIVE) {
                 return;
             }
             String callInfoText = TelecomUtils.getCallInfoText(getContext(),
-                    mPrimaryCall, mCallInfoLabel);
+                    primaryCall, mCallInfoLabel);
             setStateText(callInfoText);
             mHandler.postDelayed(this /* runnable */, DateUtils.SECOND_IN_MILLIS);
+
         }
     };
 
-    private final Runnable mStopDtmfToneRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mUiCallManager.stopDtmfTone(mPrimaryCall);
-        }
-    };
+    private final Runnable mStopDtmfToneRunnable =
+            () -> mUiCallManager.stopDtmfTone(mUiCallManager.getPrimaryCall());
 
     private final class DialpadAnimation extends Animation {
         private static final int DURATION = 300;
@@ -587,17 +588,16 @@
         private final int mScrimColor;
         private final boolean mReverse;
 
-        public DialpadAnimation(Context context, boolean reverse) {
+        DialpadAnimation(Context context, boolean reverse) {
             this(context, reverse, true);
         }
 
-        public DialpadAnimation(Context context, boolean reverse, boolean animate) {
+        DialpadAnimation(Context context, boolean reverse, boolean animate) {
             setDuration(animate ? DURATION : 0);
             setInterpolator(new AccelerateDecelerateInterpolator());
-            Resources res = context.getResources();
-            mStartingTranslation =
-                    res.getDimensionPixelOffset(R.dimen.in_call_card_dialpad_translation_x);
-            mScrimColor = res.getColor(R.color.phone_theme);
+            mStartingTranslation = context.getResources().getDimensionPixelOffset(
+                    R.dimen.in_call_card_dialpad_translation_x);
+            mScrimColor = context.getColor(R.color.phone_theme);
             mReverse = reverse;
         }
 
@@ -624,11 +624,10 @@
     }
 
     private final CallListener mCallListener = new CallListener() {
-
         @Override
         public void onCallAdded(UiCall call) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "on call added");
+                Log.d(TAG, "onCallAdded(); call: " + call);
             }
             updateCalls();
             trySpeakerAudioRouteIfNecessary();
@@ -637,7 +636,7 @@
         @Override
         public void onCallRemoved(UiCall call) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "on call removed");
+                Log.d(TAG, "onCallRemoved(); call: " + call);
             }
             mLastRemovedCall = call;
             updateCalls();
@@ -647,7 +646,9 @@
         public void onAudioStateChanged(boolean isMuted, int audioRoute,
                 int supportedAudioRouteMask) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "on audio state changed");
+                Log.d(TAG, String.format("onAudioStateChanged(); isMuted: %b, audioRoute: %d, "
+                        + " supportedAudioRouteMask: %d", isMuted, audioRoute,
+                        supportedAudioRouteMask));
             }
             mMuteButton.setActivated(isMuted);
             trySpeakerAudioRouteIfNecessary();
@@ -656,7 +657,7 @@
         @Override
         public void onStateChanged(UiCall call, int state) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onStateChanged");
+                Log.d(TAG, "onStateChanged(); call: " + call + ", state: " + state);
             }
             updateCalls();
         }
@@ -664,7 +665,7 @@
         @Override
         public void onCallUpdated(UiCall call) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "onCallUpdated");
+                Log.d(TAG, "onCallUpdated(); call: " + call);
             }
             updateCalls();
         }
diff --git a/src/com/android/car/dialer/StrequentsAdapter.java b/src/com/android/car/dialer/StrequentsAdapter.java
index 78f4d3e..981fb97 100644
--- a/src/com/android/car/dialer/StrequentsAdapter.java
+++ b/src/com/android/car/dialer/StrequentsAdapter.java
@@ -40,12 +40,11 @@
 
 /**
  * Adapter class for populating Contact data as loaded from the DB to an AA GroupingRecyclerView.
- *
- * <p>It handles two types of contacts:
- *
+ * It handles two types of contacts:
+ * <p>
  * <ul>
- *     <li>Strequent contacts (starred and/or frequent)</li>
- *     <li>Last call contact</li>
+ *     <li>Strequent contacts (starred and/or frequent)
+ *     <li>Last call contact
  * </ul>
  */
 public class StrequentsAdapter extends RecyclerView.Adapter<CallLogViewHolder>
@@ -84,10 +83,6 @@
         mStrequentsListener = listener;
     }
 
-    public void setFocusChangeListener(@Nullable View.OnFocusChangeListener listener) {
-        mFocusChangeListener = listener;
-    }
-
     public void setLastCallCursor(@Nullable Cursor cursor) {
         Log.e("StrequentsAdapter", "cursor: " + cursor);
         Log.e("StrequentsAdapter", "count: " + (cursor == null ? 0 : cursor.getCount()));
@@ -304,7 +299,7 @@
         // If we set this to 0, getRelativeTime will return null and no relative time
         // will be displayed.
         long millis = column == -1 ? 0 : cursor.getLong(column);
-        StringBuffer secondaryText = new StringBuffer();
+        StringBuilder secondaryText = new StringBuilder();
         CharSequence relativeDate = getRelativeTime(millis);
         if (!isVoicemail) {
             CharSequence type = TelecomUtils.getTypeFromNumber(mContext, number);
diff --git a/src/com/android/car/dialer/StrequentsFragment.java b/src/com/android/car/dialer/StrequentsFragment.java
index 69279c8..d091833 100644
--- a/src/com/android/car/dialer/StrequentsFragment.java
+++ b/src/com/android/car/dialer/StrequentsFragment.java
@@ -18,7 +18,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.CursorLoader;
-import android.content.Loader;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Canvas;
@@ -27,13 +26,13 @@
 import android.os.Handler;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
-import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
+
 import com.android.car.dialer.telecom.PhoneLoader;
 import com.android.car.dialer.telecom.UiCallManager;
 import com.android.car.view.PagedListView;
@@ -45,8 +44,8 @@
 public class StrequentsFragment extends Fragment {
     private static final String TAG = "Em.StrequentsFrag";
 
-    public static final String KEY_MAX_CLICKS = "max_clicks";
-    public static final int DEFAULT_MAX_CLICKS = 6;
+    private static final String KEY_MAX_CLICKS = "max_clicks";
+    private static final int DEFAULT_MAX_CLICKS = 6;
 
     private UiCallManager mUiCallManager;
     private StrequentsAdapter mAdapter;
@@ -85,7 +84,6 @@
         mListView = (PagedListView) view.findViewById(R.id.list_view);
         mListView.getLayoutManager().setOffsetRows(true);
 
-        Bundle args = getArguments();
         mSpeedialCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_SPEED_DIAL,
             mContext, (loader, cursor) -> {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -115,9 +113,10 @@
                 false, new CallLogContentObserver(new Handler()));
 
         // Maximum number of forward acting clicks the user can perform
-
-        int maxClicks = args.getInt(KEY_MAX_CLICKS,
-                DEFAULT_MAX_CLICKS /* G.maxForwardClicks.get() */);
+        Bundle args = getArguments();
+        int maxClicks = args == null
+                ? DEFAULT_MAX_CLICKS
+                : args.getInt(KEY_MAX_CLICKS, DEFAULT_MAX_CLICKS /* G.maxForwardClicks.get() */);
         // We want to show one fewer page than max clicks to allow clicking on an item,
         // but, the first page is "free" since it doesn't take any clicks to show
         final int maxPages = maxClicks < 0 ? -1 : maxClicks;
@@ -299,7 +298,7 @@
 
                 RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
                 int bottom = child.getBottom() + lp.bottomMargin
-                        + Math.round(ViewCompat.getTranslationY(child));
+                        + Math.round(child.getTranslationY());
                 int top = bottom - mDividerHeight;
 
                 if (top >= c.getHeight() || top < 0) {
diff --git a/src/com/android/car/dialer/TelecomActivity.java b/src/com/android/car/dialer/TelecomActivity.java
index 33049ed..d59fcc2 100644
--- a/src/com/android/car/dialer/TelecomActivity.java
+++ b/src/com/android/car/dialer/TelecomActivity.java
@@ -22,6 +22,8 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.app.Fragment;
 import android.support.v7.widget.SearchView;
 import android.support.v7.widget.Toolbar.LayoutParams;
@@ -57,29 +59,29 @@
  */
 public class TelecomActivity extends CarDrawerActivity implements
         DialerFragment.DialerBackButtonListener {
-    private static final String TAG = "Em.TelecomActivity";
+    private static final String TAG = "TelecomActivity";
 
     private static final String ACTION_ANSWER_CALL = "com.android.car.dialer.ANSWER_CALL";
     private static final String ACTION_END_CALL = "com.android.car.dialer.END_CALL";
+
     private static final String DIALER_BACKSTACK = "DialerBackstack";
-    private static final String FRAGMENT_CLASS_KEY = "FRAGMENT_CLASS_KEY";
+    private static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG";
+    private static final String DIALER_FRAGMENT_TAG = "DIALER_FRAGMENT_TAG";
 
     private final UiBluetoothMonitor.Listener mBluetoothListener = this::updateCurrentFragment;
 
     private UiCallManager mUiCallManager;
     private UiBluetoothMonitor mUiBluetoothMonitor;
 
-    private Fragment mCurrentFragment;
-    private String mCurrentFragmentName;
-
-    private int mLastNoHfpMessageId;
-    private StrequentsFragment mSpeedDialFragment;
-    private Fragment mOngoingCallFragment;
-
-    private DialerFragment mDialerFragment;
-    private boolean mDialerFragmentOpened;
-
-    private SearchView mSearchView;
+    /**
+     * Whether or not it is safe to make transactions on the
+     * {@link android.support.v4.app.FragmentManager}. This variable prevents a possible exception
+     * when calling commit() on the FragmentManager.
+     *
+     * <p>The default value is {@code true} because it is only after
+     * {@link #onSaveInstanceState(Bundle)} that fragment commits are not allowed.
+     */
+    private boolean mAllowFragmentCommits = true;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -93,14 +95,6 @@
 
         mUiCallManager = new UiCallManager(this);
         mUiBluetoothMonitor = new UiBluetoothMonitor(this);
-
-        if (savedInstanceState != null) {
-            mCurrentFragmentName = savedInstanceState.getString(FRAGMENT_CLASS_KEY);
-        }
-
-        if (vdebug()) {
-            Log.d(TAG, "onCreate done, mCurrentFragmentName:  " + mCurrentFragmentName);
-        }
     }
 
     @Override
@@ -131,9 +125,7 @@
 
         SearchView searchView = (SearchView) searchItem.getActionView();
         searchView.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
         searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
-        mSearchView = searchView;
 
         return super.onCreateOptionsMenu(menu);
     }
@@ -149,17 +141,17 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    protected void onStop() {
+        super.onStop();
         mUiCallManager.removeListener(mCarCallListener);
         mUiBluetoothMonitor.removeListener(mBluetoothListener);
     }
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
-        if (mCurrentFragment != null) {
-            outState.putString(FRAGMENT_CLASS_KEY, mCurrentFragmentName);
-        }
+        // A transaction can only be committed with this method prior to its containing activity
+        // saving its state.
+        mAllowFragmentCommits = false;
         super.onSaveInstanceState(outState);
     }
 
@@ -170,11 +162,15 @@
     }
 
     @Override
-    protected void onResume() {
+    protected void onStart() {
         if (vdebug()) {
-            Log.d(TAG, "onResume");
+            Log.d(TAG, "onStart");
         }
-        super.onResume();
+        super.onStart();
+
+        // Fragment commits are not allowed once the Activity's state has been saved. Once
+        // onStart() has been called, the FragmentManager should now allow commits.
+        mAllowFragmentCommits = true;
 
         // Update the current fragment before handling the intent so that any UI updates in
         // handleIntent() is not overridden by updateCurrentFragment().
@@ -185,27 +181,6 @@
         mUiBluetoothMonitor.addListener(mBluetoothListener);
     }
 
-    // TODO: move to base class.
-    private void setContentFragmentWithAnimations(Fragment fragment, int enter, int exit) {
-        if (vdebug()) {
-            Log.d(TAG, "setContentFragmentWithAnimations: " + fragment);
-        }
-
-        maybeHideDialer();
-
-        getSupportFragmentManager().beginTransaction()
-                .setCustomAnimations(enter, exit)
-                .replace(getContentContainerId(), fragment)
-                .commitAllowingStateLoss();
-
-        mCurrentFragmentName = fragment.getClass().getSimpleName();
-        mCurrentFragment = fragment;
-
-        if (vdebug()) {
-            Log.d(TAG, "setContentFragmentWithAnimations, fragmentName:" + mCurrentFragmentName);
-        }
-    }
-
     private void handleIntent() {
         Intent intent = getIntent();
         String action = intent != null ? intent.getAction() : null;
@@ -240,14 +215,15 @@
 
             case Intent.ACTION_DIAL:
                 String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
-                if (!(mCurrentFragment instanceof NoHfpFragment)) {
-                    showDialerWithNumber(number);
+                if (!(getCurrentFragment() instanceof NoHfpFragment)) {
+                    showDialer(number);
                 }
                 break;
 
             case SEARCH_SUGGESTION_CLICKED:
                 Uri contactUri = intent.getData();
-                showContactDetailFragment(contactUri);
+                setContentFragment(
+                        ContactDetailsFragment.newInstance(contactUri, mUiCallManager));
                 break;
 
             default:
@@ -258,15 +234,13 @@
     }
 
     /**
-     * Will switch to the drawer or no-hfp fragment as necessary.
+     * Updates the content fragment of this Activity based on the state of the application.
      */
     private void updateCurrentFragment() {
         if (vdebug()) {
-            Log.d(TAG, "updateCurrentFragment");
+            Log.d(TAG, "updateCurrentFragment()");
         }
 
-        // TODO: do nothing when activity isFinishing() == true.
-
         boolean callEmpty = mUiCallManager.getCalls().isEmpty();
         if (!mUiBluetoothMonitor.isBluetoothEnabled() && callEmpty) {
             showNoHfpFragment(R.string.bluetooth_disabled);
@@ -279,15 +253,13 @@
 
             if (vdebug()) {
                 Log.d(TAG, "ongoingCall: " + ongoingCall + ", mCurrentFragment: "
-                        + mCurrentFragment);
+                        + getCurrentFragment());
             }
 
-            if (ongoingCall == null && mCurrentFragment instanceof OngoingCallFragment) {
+            if (ongoingCall == null && getCurrentFragment() instanceof OngoingCallFragment) {
                 showSpeedDialFragment();
             } else if (ongoingCall != null) {
                 showOngoingCallFragment();
-            } else if (DialerFragment.class.getSimpleName().equals(mCurrentFragmentName)) {
-                showDialer();
             } else {
                 showSpeedDialFragment();
             }
@@ -303,20 +275,15 @@
             Log.d(TAG, "showSpeedDialFragment");
         }
 
-        if (mCurrentFragment instanceof StrequentsFragment) {
+        if (!mAllowFragmentCommits || getCurrentFragment() instanceof StrequentsFragment) {
             return;
         }
 
-        if (mSpeedDialFragment == null) {
-            mSpeedDialFragment = StrequentsFragment.newInstance(mUiCallManager);
-            Bundle args = new Bundle();
-            mSpeedDialFragment.setArguments(args);
-        }
-
-        if (mCurrentFragment instanceof DialerFragment) {
-            setContentFragmentWithSlideAndDelayAnimation(mSpeedDialFragment);
+        Fragment fragment = StrequentsFragment.newInstance(mUiCallManager);
+        if (getCurrentFragment() instanceof DialerFragment) {
+            setContentFragmentWithSlideAndDelayAnimation(fragment);
         } else {
-            setContentFragmentWithFadeAnimation(mSpeedDialFragment);
+            setContentFragmentWithFadeAnimation(fragment);
         }
     }
 
@@ -324,40 +291,39 @@
         if (vdebug()) {
             Log.d(TAG, "showOngoingCallFragment");
         }
-        if (mCurrentFragment instanceof OngoingCallFragment) {
+        if (!mAllowFragmentCommits || getCurrentFragment() instanceof OngoingCallFragment) {
             closeDrawer();
             return;
         }
 
-        if (mOngoingCallFragment == null) {
-            mOngoingCallFragment =
-                    OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor);
-        }
-
-        setContentFragmentWithFadeAnimation(mOngoingCallFragment);
+        Fragment fragment = OngoingCallFragment.newInstance(mUiCallManager, mUiBluetoothMonitor);
+        setContentFragmentWithFadeAnimation(fragment);
         closeDrawer();
     }
 
-    /**
-     * Displays the {@link DialerFragment} on top of the contents of the TelecomActivity.
-     */
     private void showDialer() {
         if (vdebug()) {
             Log.d(TAG, "showDialer");
         }
 
-        if (mDialerFragmentOpened) {
+        showDialer(null /* dialNumber */);
+    }
+
+    /**
+     * Displays the {@link DialerFragment} and initialize it with the given phone number.
+     */
+    private void showDialer(@Nullable String dialNumber) {
+        if (vdebug()) {
+            Log.d(TAG, "showDialer with number: " + dialNumber);
+        }
+
+        if (!mAllowFragmentCommits ||
+                getSupportFragmentManager().findFragmentByTag(DIALER_FRAGMENT_TAG) != null) {
             return;
         }
 
-        if (mDialerFragment == null) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "showDialer: creating dialer");
-            }
-
-            mDialerFragment = DialerFragment.newInstance(mUiCallManager);
-            mDialerFragment.setDialerBackButtonListener(this);
-        }
+        Fragment fragment =
+                DialerFragment.newInstance(mUiCallManager, this /* listener */, dialNumber);
 
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "adding dialer to fragment backstack");
@@ -367,11 +333,9 @@
         getSupportFragmentManager().beginTransaction()
                 .setCustomAnimations(R.anim.telecom_slide_in, R.anim.telecom_slide_out,
                         R.anim.telecom_slide_in, R.anim.telecom_slide_out)
-                .add(getContentContainerId(), mDialerFragment)
+                .add(getContentContainerId(), fragment, DIALER_FRAGMENT_TAG)
                 .addToBackStack(DIALER_BACKSTACK)
-                .commitAllowingStateLoss();
-
-        mDialerFragmentOpened = true;
+                .commit();
 
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "done adding fragment to backstack");
@@ -382,10 +346,10 @@
      * Checks if the dialpad fragment is opened and hides it if it is.
      */
     private void maybeHideDialer() {
-        if (mDialerFragmentOpened) {
-            // Dismiss the dialer by removing it from the back stack.
+        // The dialer is the only fragment to be added to the back stack. Dismiss the dialer by
+        // removing it from the back stack.
+        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
             getSupportFragmentManager().popBackStack();
-            mDialerFragmentOpened = false;
         }
     }
 
@@ -394,30 +358,19 @@
         maybeHideDialer();
     }
 
-    private void showDialerWithNumber(String number) {
-        showDialer();
-        mDialerFragment.setDialNumber(number);
-    }
-
-    private void showNoHfpFragment(int stringResId) {
-        if (mCurrentFragment instanceof NoHfpFragment && stringResId == mLastNoHfpMessageId) {
+    private void showNoHfpFragment(@StringRes int stringResId) {
+        if (!mAllowFragmentCommits) {
             return;
         }
 
-        mLastNoHfpMessageId = stringResId;
         String errorMessage = getString(stringResId);
-        NoHfpFragment frag = new NoHfpFragment();
-        frag.setErrorMessage(errorMessage);
-        setContentFragment(frag);
-        mCurrentFragment = frag;
-    }
+        Fragment currentFragment = getCurrentFragment();
 
-
-    private void showContactDetailFragment(Uri contactUri) {
-        ContactDetailsFragment fragment =
-                ContactDetailsFragment.newInstance(contactUri, mUiCallManager);
-        setContentFragment(fragment);
-        mCurrentFragment = fragment;
+        if (currentFragment instanceof NoHfpFragment) {
+            ((NoHfpFragment) currentFragment).setErrorMessage(errorMessage);
+        } else {
+            setContentFragment(NoHfpFragment.newInstance(errorMessage));
+        }
     }
 
     private void setContentFragmentWithSlideAndDelayAnimation(Fragment fragment) {
@@ -436,6 +389,41 @@
                 R.anim.telecom_fade_in, R.anim.telecom_fade_out);
     }
 
+    private void setContentFragmentWithAnimations(Fragment fragment, int enter, int exit) {
+        if (vdebug()) {
+            Log.d(TAG, "setContentFragmentWithAnimations: " + fragment);
+        }
+
+        maybeHideDialer();
+        getSupportFragmentManager().beginTransaction()
+                .setCustomAnimations(enter, exit)
+                .replace(getContentContainerId(), fragment, CONTENT_FRAGMENT_TAG)
+                .commitNow();
+    }
+
+    /**
+     * Sets the fragment that will be shown as the main content of this Activity. Note that this
+     * fragment is not always visible. In particular, the dialer fragment can show up on top of this
+     * fragment.
+     */
+    private void setContentFragment(Fragment fragment) {
+        maybeHideDialer();
+        getSupportFragmentManager().beginTransaction()
+                .replace(getContentContainerId(), fragment, CONTENT_FRAGMENT_TAG)
+                .commitNow();
+    }
+
+    /**
+     * Returns the fragment that is currently being displayed as the content view. Note that this
+     * is not necessarily the fragment that is visible. For example, the returned fragment
+     * could be the content, but the dial fragment is being displayed on top of it. Check for
+     * the existence of the dial fragment with the TAG {@link #DIALER_FRAGMENT_TAG}.
+     */
+    @Nullable
+    private Fragment getCurrentFragment() {
+        return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
+    }
+
     private final CallListener mCarCallListener = new UiCallManager.CallListener() {
         @Override
         public void onCallAdded(UiCall call) {
@@ -470,12 +458,6 @@
         }
     };
 
-    private void setContentFragment(Fragment fragment) {
-        getSupportFragmentManager().beginTransaction()
-                .replace(getContentContainerId(), fragment)
-                .commit();
-    }
-
     private static boolean vdebug() {
         return Log.isLoggable(TAG, Log.DEBUG);
     }
@@ -506,9 +488,10 @@
 
         @Override
         public void populateViewHolder(DrawerItemViewHolder holder, int position) {
-            holder.getTitle().setText(mItems.get(position).mTitle);
-            holder.getText().setText(mItems.get(position).mText);
-            holder.getIcon().setImageBitmap(mItems.get(position).mIcon);
+            CallLogListingTask.CallLogItem item = mItems.get(position);
+            holder.getTitle().setText(item.mTitle);
+            holder.getText().setText(item.mText);
+            holder.getIcon().setImageBitmap(item.mIcon);
         }
 
         @Override