Merge changes Idbaca1df,Ie7824ce2,Id24b2318,Ib40632fe,I292f23ea, ...

* changes:
  Do no preload dialpad fragment when opening search through search bar.
  Add logging to NUI.
  Implemented hangouts connection into GoogleMainActivity.
  Created a "Metrics" interface.
  Fix the icon & the label for blocked spam numbers in the new call log.
  Move RttChatBot to simulator.
  Update RTT call with real name/number and timer.
  Include both PHOTO_URI and PHOTO_THUMBNAIL_URI in Cp2Info.
  Add simulator RTT call.
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index 0f00a5d..35f8540 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -26,6 +26,7 @@
 import com.android.dialer.feedback.stub.StubFeedbackModule;
 import com.android.dialer.glidephotomanager.GlidePhotoManagerModule;
 import com.android.dialer.inject.ContextModule;
+import com.android.dialer.metrics.StubMetricsModule;
 import com.android.dialer.phonelookup.PhoneLookupModule;
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
 import com.android.dialer.precall.impl.PreCallModule;
@@ -63,6 +64,7 @@
     StubDuoModule.class,
     StubEnrichedCallModule.class,
     StubNewBubbleModule.class,
+    StubMetricsModule.class,
     StubFeedbackModule.class,
     StubMapsModule.class,
     StubSimSuggestionModule.class,
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 3e7db9d..cd95c3e 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -27,6 +27,7 @@
 import com.android.dialer.feedback.FeedbackComponent;
 import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
 import com.android.dialer.main.MainComponent;
+import com.android.dialer.metrics.MetricsComponent;
 import com.android.dialer.phonelookup.PhoneLookupComponent;
 import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent;
 import com.android.dialer.precall.PreCallComponent;
@@ -59,6 +60,7 @@
         GlidePhotoManagerComponent.HasComponent,
         MainComponent.HasComponent,
         MapsComponent.HasComponent,
+        MetricsComponent.HasComponent,
         NewBubbleComponent.HasComponent,
         PhoneLookupComponent.HasComponent,
         PhoneNumberGeoUtilComponent.HasComponent,
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index d4520f33..497d977 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -26,6 +26,7 @@
 import com.android.dialer.feedback.stub.StubFeedbackModule;
 import com.android.dialer.glidephotomanager.GlidePhotoManagerModule;
 import com.android.dialer.inject.ContextModule;
+import com.android.dialer.metrics.StubMetricsModule;
 import com.android.dialer.phonelookup.PhoneLookupModule;
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
 import com.android.dialer.precall.impl.PreCallModule;
@@ -67,6 +68,7 @@
     StubDuoModule.class,
     StubEnrichedCallModule.class,
     StubFeedbackModule.class,
+    StubMetricsModule.class,
     StubNewBubbleModule.class,
     StubSimSuggestionModule.class,
     StubSpamModule.class,
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 52570c0..8dec437 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -28,18 +28,17 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import com.android.dialer.DialerPhoneNumber;
-import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.CallLogMutations;
 import com.android.dialer.calllog.datasources.util.RowCombiner;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
 import com.android.dialer.phonelookup.PhoneLookup;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
-import com.android.dialer.phonelookup.consolidator.PhoneLookupInfoConsolidator;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
 import com.google.common.collect.ImmutableMap;
@@ -573,23 +572,8 @@
   }
 
   private void updateContentValues(ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) {
-    PhoneLookupInfoConsolidator phoneLookupInfoConsolidator =
-        new PhoneLookupInfoConsolidator(phoneLookupInfo);
     contentValues.put(
         AnnotatedCallLog.NUMBER_ATTRIBUTES,
-        NumberAttributes.newBuilder()
-            .setName(phoneLookupInfoConsolidator.getName())
-            .setPhotoUri(phoneLookupInfoConsolidator.getPhotoUri())
-            .setPhotoId(phoneLookupInfoConsolidator.getPhotoId())
-            .setLookupUri(phoneLookupInfoConsolidator.getLookupUri())
-            .setNumberTypeLabel(phoneLookupInfoConsolidator.getNumberLabel())
-            .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
-            .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
-            .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
-            .setIsSpam(phoneLookupInfoConsolidator.isSpam())
-            .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
-            .setIsCp2InfoIncomplete(phoneLookupInfoConsolidator.isCp2LocalInfoIncomplete())
-            .build()
-            .toByteArray());
+        NumberAttributesConverter.fromPhoneLookupInfo(phoneLookupInfo).build().toByteArray());
   }
 }
diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
index 5083a95..69c4319 100644
--- a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
+++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
@@ -23,8 +23,8 @@
 import android.support.annotation.VisibleForTesting;
 import android.util.ArrayMap;
 import com.android.dialer.DialerPhoneNumber;
-import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
@@ -33,7 +33,6 @@
 import com.android.dialer.inject.ApplicationContext;
 import com.android.dialer.phonelookup.PhoneLookup;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
-import com.android.dialer.phonelookup.consolidator.PhoneLookupInfoConsolidator;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract;
 import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
 import com.google.common.collect.ImmutableMap;
@@ -198,23 +197,8 @@
 
   private CoalescedRow applyPhoneLookupInfoToRow(
       PhoneLookupInfo phoneLookupInfo, CoalescedRow row) {
-    PhoneLookupInfoConsolidator phoneLookupInfoConsolidator =
-        new PhoneLookupInfoConsolidator(phoneLookupInfo);
     return row.toBuilder()
-        .setNumberAttributes(
-            // TODO(zachh): Put this in a common location.
-            NumberAttributes.newBuilder()
-                .setName(phoneLookupInfoConsolidator.getName())
-                .setPhotoUri(phoneLookupInfoConsolidator.getPhotoUri())
-                .setPhotoId(phoneLookupInfoConsolidator.getPhotoId())
-                .setLookupUri(phoneLookupInfoConsolidator.getLookupUri())
-                .setNumberTypeLabel(phoneLookupInfoConsolidator.getNumberLabel())
-                .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
-                .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
-                .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
-                .setIsSpam(phoneLookupInfoConsolidator.isSpam())
-                .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
-                .build())
+        .setNumberAttributes(NumberAttributesConverter.fromPhoneLookupInfo(phoneLookupInfo).build())
         .build();
   }
 }
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index 737b1d3..ab851cb 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -70,7 +70,8 @@
    * <ul>
    *   <li>For numbers that are not spam or blocked: (Duo video, )?$Label|$Location • Date
    *   <li>For blocked non-spam numbers: Blocked • (Duo video, )?$Label|$Location • Date
-   *   <li>For spam numbers: Spam • (Duo video, )?$Label • Date
+   *   <li>For spam but not blocked numbers: Spam • (Duo video, )?$Label • Date
+   *   <li>For blocked spam numbers: Blocked • Spam • (Duo video, )?$Label • Date
    * </ul>
    *
    * <p>Examples:
@@ -84,6 +85,7 @@
    *   <li>Blocked • Brooklyn, NJ • 10 min ago
    *   <li>Spam • Mobile • Now
    *   <li>Spam • Now
+   *   <li>Blocked • Spam • Mobile • Now
    *   <li>Brooklyn, NJ • Jan 15
    * </ul>
    *
@@ -93,11 +95,11 @@
       Context context, Clock clock, CoalescedRow row) {
     List<CharSequence> components = new ArrayList<>();
 
-    // If a number is both spam and blocked, only show "Spam".
+    if (row.numberAttributes().getIsBlocked()) {
+      components.add(context.getText(R.string.new_call_log_secondary_blocked));
+    }
     if (row.numberAttributes().getIsSpam()) {
       components.add(context.getText(R.string.new_call_log_secondary_spam));
-    } else if (row.numberAttributes().getIsBlocked()) {
-      components.add(context.getText(R.string.new_call_log_secondary_blocked));
     }
 
     components.add(getNumberTypeLabel(context, row));
@@ -121,8 +123,10 @@
      *     (Duo video, )?$Label|$Location [• NumberIfNoName]?
      *   For blocked non-spam numbers:
      *     Blocked • (Duo video, )?$Label|$Location [• NumberIfNoName]?
-     *   For spam numbers:
+     *   For spam but not blocked numbers:
      *     Spam • (Duo video, )?$Label [• NumberIfNoName]?
+     *   For blocked spam numbers:
+     *     Blocked • Spam • (Duo video, )?$Label [• NumberIfNoName]?
      *
      * The number is shown at the end if there is no name for the entry. (It is shown in primary
      * text otherwise.)
@@ -139,11 +143,11 @@
      */
     List<CharSequence> components = new ArrayList<>();
 
-    // If a number is both spam and blocked, only show "Spam".
+    if (row.numberAttributes().getIsBlocked()) {
+      components.add(context.getText(R.string.new_call_log_secondary_blocked));
+    }
     if (row.numberAttributes().getIsSpam()) {
       components.add(context.getText(R.string.new_call_log_secondary_spam));
-    } else if (row.numberAttributes().getIsBlocked()) {
-      components.add(context.getText(R.string.new_call_log_secondary_blocked));
     }
 
     components.add(getNumberTypeLabel(context, row));
diff --git a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
index bed1edd..efd1d72 100644
--- a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
+++ b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
@@ -16,13 +16,16 @@
 
 package com.android.dialer.calllogutils;
 
+import android.text.TextUtils;
 import com.android.dialer.NumberAttributes;
 import com.android.dialer.glidephotomanager.PhotoInfo;
+import com.android.dialer.phonelookup.PhoneLookupInfo;
+import com.android.dialer.phonelookup.consolidator.PhoneLookupInfoConsolidator;
 
 /** Converts {@link NumberAttributes} to {@link PhotoInfo} */
 public final class NumberAttributesConverter {
 
-  /** Converts to {@link PhotoInfo.Builder} */
+  /** Converts {@link NumberAttributes} to {@link PhotoInfo.Builder} */
   public static PhotoInfo.Builder toPhotoInfoBuilder(NumberAttributes numberAttributes) {
     return PhotoInfo.builder()
         .setName(numberAttributes.getName())
@@ -34,4 +37,25 @@
         .setIsVoicemail(numberAttributes.getIsVoicemail())
         .setIsBlocked(numberAttributes.getIsBlocked());
   }
+
+  /** Converts {@link PhoneLookupInfo} to {@link NumberAttributes.Builder} */
+  public static NumberAttributes.Builder fromPhoneLookupInfo(PhoneLookupInfo phoneLookupInfo) {
+    PhoneLookupInfoConsolidator phoneLookupInfoConsolidator =
+        new PhoneLookupInfoConsolidator(phoneLookupInfo);
+    return NumberAttributes.newBuilder()
+        .setName(phoneLookupInfoConsolidator.getName())
+        .setPhotoUri(
+            !TextUtils.isEmpty(phoneLookupInfoConsolidator.getPhotoThumbnailUri())
+                ? phoneLookupInfoConsolidator.getPhotoThumbnailUri()
+                : phoneLookupInfoConsolidator.getPhotoUri())
+        .setPhotoId(phoneLookupInfoConsolidator.getPhotoId())
+        .setLookupUri(phoneLookupInfoConsolidator.getLookupUri())
+        .setNumberTypeLabel(phoneLookupInfoConsolidator.getNumberLabel())
+        .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
+        .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
+        .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
+        .setIsSpam(phoneLookupInfoConsolidator.isSpam())
+        .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
+        .setIsCp2InfoIncomplete(phoneLookupInfoConsolidator.isCp2LocalInfoIncomplete());
+  }
 }
diff --git a/java/com/android/dialer/common/res/values/strings.xml b/java/com/android/dialer/common/res/values/strings.xml
index 770f42f..53a2b56 100644
--- a/java/com/android/dialer/common/res/values/strings.xml
+++ b/java/com/android/dialer/common/res/values/strings.xml
@@ -17,4 +17,6 @@
 <resources>
   <string name="network_name_wifi">Wifi</string>
   <string name="network_name_mobile">Mobile</string>
+  <!-- Content description for the overflow menu button. [CHAR LIMIT=NONE] -->
+  <string name="content_description_overflow">More options</string>
 </resources>
diff --git a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
index c6d9205..10c4dfb 100644
--- a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
+++ b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
@@ -68,13 +68,13 @@
     // Warning: Glide ignores extra attributes on BitmapDrawable such as tint and draw the bitmap
     // directly so be sure not to set tint in the XML of any drawable referenced below.
 
-    // The spam status takes precedence over whether the number is blocked.
-    if (photoInfo.isSpam()) {
-      return requestManager.load(R.drawable.ic_report_red_48dp);
-    }
+    // Whether the number is blocked takes precedence over the spam status.
     if (photoInfo.isBlocked()) {
       return requestManager.load(R.drawable.ic_block_grey_48dp);
     }
+    if (photoInfo.isSpam()) {
+      return requestManager.load(R.drawable.ic_report_red_48dp);
+    }
     if (!TextUtils.isEmpty(photoInfo.photoUri())) {
       return requestManager.load(parseUri(photoInfo.photoUri()));
     }
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index f839b13..635d8fd 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -12,7 +12,7 @@
   // Event enums to be used for Impression Logging in Dialer.
   // It's perfectly acceptable for this enum to be large
   // Values should be from 1000 to 100000.
-  // Next Tag: 1327
+  // Next Tag: 1341
   enum Type {
     UNKNOWN_AOSP_EVENT_TYPE = 1000;
 
@@ -657,5 +657,23 @@
     DUO_CALL_LOG_SET_UP_INSTALL_SHOWN = 1324;
     DUO_CALL_LOG_SET_UP_ACTIVATE_SHOWN = 1325;
     DUO_CALL_LOG_INVITE_SHOWN = 1326;
+
+    // NUI bottom navigation bar
+    NUI_SWITCH_TAB_TO_FAVORITE = 1327;
+    NUI_SWITCH_TAB_TO_CALL_LOG = 1328;
+    NUI_SWITCH_TAB_TO_CONTACTS = 1329;
+    NUI_SWITCH_TAB_TO_VOICEMAIL = 1330;
+    // NUI search
+    NUI_TOUCH_DIALPAD_SEARCH_LIST_TO_CLOSE_SEARCH_AND_DIALPAD = 1331;
+    NUI_TOUCH_DIALPAD_SEARCH_LIST_TO_HIDE_DIALPAD = 1332;
+    NUI_TOUCH_SEARCH_LIST_TO_CLOSE_SEARCH = 1333;
+    NUI_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD = 1334;
+    NUI_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH = 1335;
+    NUI_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH_AND_DIALPAD = 1336;
+    NUI_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD = 1337;
+    NUI_CLICK_SEARCH_BAR = 1338;
+    NUI_CLICK_SEARCH_BAR_VOICE_BUTTON = 1339;
+    // NUI FAB
+    NUI_CLICK_FAB_TO_OPEN_DIALPAD = 1340;
   }
 }
diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java
index 6457b60..c1a328a 100644
--- a/java/com/android/dialer/main/MainActivityPeer.java
+++ b/java/com/android/dialer/main/MainActivityPeer.java
@@ -28,6 +28,8 @@
 
   void onActivityStop();
 
+  void onActivityDestroyed();
+
   void onNewIntent(Intent intent);
 
   void onActivityResult(int requestCode, int resultCode, Intent data);
diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java
index 7098f88..9b734f4 100644
--- a/java/com/android/dialer/main/impl/MainSearchController.java
+++ b/java/com/android/dialer/main/impl/MainSearchController.java
@@ -40,6 +40,7 @@
 import com.android.dialer.dialpadview.DialpadFragment;
 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener;
+import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.logging.ScreenEvent;
 import com.android.dialer.main.impl.bottomnav.BottomNavBar;
@@ -204,14 +205,23 @@
   public void onSearchListTouch() {
     if (isDialpadVisible()) {
       if (TextUtils.isEmpty(getDialpadFragment().getQuery())) {
+        Logger.get(mainActivity)
+            .logImpression(
+                DialerImpression.Type.NUI_TOUCH_DIALPAD_SEARCH_LIST_TO_CLOSE_SEARCH_AND_DIALPAD);
         closeSearch(true);
       } else {
+        Logger.get(mainActivity)
+            .logImpression(DialerImpression.Type.NUI_TOUCH_DIALPAD_SEARCH_LIST_TO_HIDE_DIALPAD);
         hideDialpad(/* animate=*/ true, /* bottomNavVisible=*/ false);
       }
     } else if (isSearchVisible()) {
       if (TextUtils.isEmpty(toolbar.getQuery())) {
+        Logger.get(mainActivity)
+            .logImpression(DialerImpression.Type.NUI_TOUCH_SEARCH_LIST_TO_CLOSE_SEARCH);
         closeSearch(true);
       } else {
+        Logger.get(mainActivity)
+            .logImpression(DialerImpression.Type.NUI_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD);
         toolbar.hideKeyboard();
       }
     }
@@ -225,10 +235,17 @@
   public boolean onBackPressed() {
     if (isDialpadVisible() && !TextUtils.isEmpty(getDialpadFragment().getQuery())) {
       LogUtil.i("MainSearchController#onBackPressed", "Dialpad visible with query");
+      Logger.get(mainActivity)
+          .logImpression(DialerImpression.Type.NUI_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD);
       hideDialpad(/* animate=*/ true, /* bottomNavVisible=*/ false);
       return true;
     } else if (isSearchVisible()) {
       LogUtil.i("MainSearchController#onBackPressed", "Search is visible");
+      Logger.get(mainActivity)
+          .logImpression(
+              isDialpadVisible()
+                  ? DialerImpression.Type.NUI_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH_AND_DIALPAD
+                  : DialerImpression.Type.NUI_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH);
       closeSearch(true);
       return true;
     } else {
@@ -252,16 +269,13 @@
     mainActivity.getFragmentManager().beginTransaction().remove(getSearchFragment()).commit();
 
     // Clear the dialpad so the phone number isn't persisted between search sessions.
-    getDialpadFragment().clearDialpad();
+    if (getDialpadFragment() != null) {
+      getDialpadFragment().clearDialpad();
+    }
   }
 
-  /**
-   * Returns {@link DialpadFragment}.
-   *
-   * <p>Unless this method is being called for the first time in {@link #openSearch(Optional)} or
-   * {@link #showDialpad(boolean)}, it should never return null.
-   */
-  private DialpadFragment getDialpadFragment() {
+  @Nullable
+  protected DialpadFragment getDialpadFragment() {
     return (DialpadFragment)
         mainActivity.getFragmentManager().findFragmentByTag(DIALPAD_FRAGMENT_TAG);
   }
@@ -297,6 +311,7 @@
    */
   @Override
   public void onSearchBarClicked() {
+    Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR);
     openSearch(Optional.absent());
   }
 
@@ -316,14 +331,6 @@
       transaction.show(getSearchFragment());
     }
 
-    // Add the dialpad fragment but keep it hidden
-    if (getDialpadFragment() == null) {
-      DialpadFragment dialpadFragment = new DialpadFragment();
-      transaction
-          .add(R.id.dialpad_fragment_container, dialpadFragment, DIALPAD_FRAGMENT_TAG)
-          .hide(dialpadFragment);
-    }
-
     transaction.commit();
   }
 
@@ -350,6 +357,7 @@
 
   @Override
   public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) {
+    Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR_VOICE_BUTTON);
     try {
       Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
       mainActivity.startActivityForResult(voiceIntent, ActivityRequestCodes.DIALTACTS_VOICE_SEARCH);
diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java
index 0a85667..ed67df9 100644
--- a/java/com/android/dialer/main/impl/NewMainActivityPeer.java
+++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java
@@ -60,6 +60,9 @@
   public void onActivityStop() {}
 
   @Override
+  public void onActivityDestroyed() {}
+
+  @Override
   public void onNewIntent(Intent intent) {}
 
   @Override
diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
index c46e61b..07c7185 100644
--- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java
+++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
@@ -66,6 +66,8 @@
 import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback;
 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener;
 import com.android.dialer.interactions.PhoneNumberInteraction;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
 import com.android.dialer.main.MainActivityPeer;
 import com.android.dialer.main.impl.bottomnav.BottomNavBar;
 import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener;
@@ -154,6 +156,7 @@
 
   @Override
   public void onActivityCreate(Bundle savedInstanceState) {
+    LogUtil.enterBlock("OldMainActivityPeer.onActivityCreate");
     mainActivity.setContentView(R.layout.main_activity);
     initUiListeners();
     initLayout(savedInstanceState);
@@ -173,14 +176,19 @@
     snackbarContainer = mainActivity.findViewById(R.id.coordinator_layout);
 
     FloatingActionButton fab = mainActivity.findViewById(R.id.fab);
-    fab.setOnClickListener(v -> searchController.showDialpad(true));
+    fab.setOnClickListener(
+        v -> {
+          Logger.get(mainActivity)
+              .logImpression(DialerImpression.Type.NUI_CLICK_FAB_TO_OPEN_DIALPAD);
+          searchController.showDialpad(true);
+        });
 
     MainToolbar toolbar = mainActivity.findViewById(R.id.toolbar);
     mainActivity.setSupportActionBar(mainActivity.findViewById(R.id.toolbar));
 
     bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar);
     MainBottomNavBarBottomNavTabListener bottomNavTabListener =
-        new MainBottomNavBarBottomNavTabListener(mainActivity.getFragmentManager());
+        new MainBottomNavBarBottomNavTabListener(mainActivity, mainActivity.getFragmentManager());
     bottomNav.addOnTabSelectedListener(bottomNavTabListener);
 
     callLogFragmentListener =
@@ -191,7 +199,7 @@
     searchController = getNewMainSearchController(bottomNav, fab, toolbar);
     toolbar.setSearchBarListener(searchController);
 
-    onDialpadQueryChangedListener = new MainOnDialpadQueryChangedListener(searchController);
+    onDialpadQueryChangedListener = getNewOnDialpadQueryChangedListener(searchController);
     dialpadListener =
         new MainDialpadListener(mainActivity, searchController, getLastOutgoingCallListener);
     searchFragmentListener = new MainSearchFragmentListener(searchController);
@@ -222,6 +230,7 @@
 
   @Override
   public void onNewIntent(Intent intent) {
+    LogUtil.enterBlock("OldMainActivityPeer.onNewIntent");
     showTabOnIntent(intent);
   }
 
@@ -285,6 +294,9 @@
         mainActivity.getSystemService(KeyguardManager.class).isKeyguardLocked());
   }
 
+  @Override
+  public void onActivityDestroyed() {}
+
   private void showPostCallPrompt() {
     if (TelecomUtil.isInManagedCall(mainActivity)) {
       // No prompt to show if the user is in a call
@@ -309,10 +321,15 @@
 
   @Override
   public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    LogUtil.i(
+        "OldMainActivityPeer.onActivityResult",
+        "requestCode:%d, resultCode:%d",
+        requestCode,
+        resultCode);
     if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) {
       searchController.onVoiceResults(resultCode, data);
     } else {
-      LogUtil.e("MainActivity.onActivityResult", "Unknown request code: " + requestCode);
+      LogUtil.e("OldMainActivityPeer.onActivityResult", "Unknown request code: " + requestCode);
     }
   }
 
@@ -350,6 +367,8 @@
       return (T) onPhoneNumberPickerActionListener;
     } else if (callbackInterface.isInstance(oldSpeedDialFragmentHost)) {
       return (T) oldSpeedDialFragmentHost;
+    } else if (callbackInterface.isInstance(searchController)) {
+      return (T) searchController;
     } else {
       return null;
     }
@@ -360,6 +379,11 @@
     return new MainSearchController(mainActivity, bottomNavBar, fab, mainToolbar);
   }
 
+  public MainOnDialpadQueryChangedListener getNewOnDialpadQueryChangedListener(
+      MainSearchController mainSearchController) {
+    return new MainOnDialpadQueryChangedListener(mainSearchController);
+  }
+
   /** @see OnContactSelectedListener */
   private static final class MainOnContactSelectedListener implements OnContactSelectedListener {
 
@@ -378,12 +402,12 @@
   }
 
   /** @see OnDialpadQueryChangedListener */
-  private static final class MainOnDialpadQueryChangedListener
+  protected static class MainOnDialpadQueryChangedListener
       implements OnDialpadQueryChangedListener {
 
     private final MainSearchController searchController;
 
-    MainOnDialpadQueryChangedListener(MainSearchController searchController) {
+    protected MainOnDialpadQueryChangedListener(MainSearchController searchController) {
       this.searchController = searchController;
     }
 
@@ -794,13 +818,20 @@
     private static final String VOICEMAIL_TAG = "voicemail";
 
     private final FragmentManager fragmentManager;
+    private final Context context;
+    @TabIndex private int selectedTab = -1;
 
-    private MainBottomNavBarBottomNavTabListener(FragmentManager fragmentManager) {
+    private MainBottomNavBarBottomNavTabListener(Context context, FragmentManager fragmentManager) {
       this.fragmentManager = fragmentManager;
+      this.context = context;
     }
 
     @Override
     public void onSpeedDialSelected() {
+      if (selectedTab != TabIndex.SPEED_DIAL) {
+        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_FAVORITE);
+        selectedTab = TabIndex.SPEED_DIAL;
+      }
       hideAllFragments();
       Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
       if (fragment == null) {
@@ -815,6 +846,10 @@
 
     @Override
     public void onCallLogSelected() {
+      if (selectedTab != TabIndex.CALL_LOG) {
+        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CALL_LOG);
+        selectedTab = TabIndex.CALL_LOG;
+      }
       hideAllFragments();
       CallLogFragment fragment = (CallLogFragment) fragmentManager.findFragmentByTag(CALL_LOG_TAG);
       if (fragment == null) {
@@ -829,6 +864,10 @@
 
     @Override
     public void onContactsSelected() {
+      if (selectedTab != TabIndex.CONTACTS) {
+        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CONTACTS);
+        selectedTab = TabIndex.CONTACTS;
+      }
       hideAllFragments();
       ContactsFragment fragment =
           (ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG);
@@ -847,6 +886,10 @@
 
     @Override
     public void onVoicemailSelected() {
+      if (selectedTab != TabIndex.VOICEMAIL) {
+        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_VOICEMAIL);
+        selectedTab = TabIndex.VOICEMAIL;
+      }
       hideAllFragments();
       VisualVoicemailCallLogFragment fragment =
           (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG);
diff --git a/java/com/android/dialer/metrics/Metrics.java b/java/com/android/dialer/metrics/Metrics.java
new file mode 100644
index 0000000..3922a8c
--- /dev/null
+++ b/java/com/android/dialer/metrics/Metrics.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.dialer.metrics;
+
+import android.app.Application;
+import android.content.Context;
+
+/** Logs metrics. */
+public interface Metrics {
+
+  /** Start a timer. */
+  void startTimer(Context context, String timerEventName);
+
+  /** Stop a timer. */
+  void stopTimer(String timerEventName);
+
+  /** Record memory. */
+  void recordMemory(String memoryEventName);
+
+  /** Initiazer for metrics. */
+  interface Initializer {
+    /** Initialize metrics for the application . */
+    void initialize(Application application);
+  }
+}
diff --git a/java/com/android/dialer/metrics/MetricsComponent.java b/java/com/android/dialer/metrics/MetricsComponent.java
new file mode 100644
index 0000000..f371297
--- /dev/null
+++ b/java/com/android/dialer/metrics/MetricsComponent.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 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.dialer.metrics;
+
+import android.content.Context;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+/** Component for metrics. */
+@Subcomponent
+public abstract class MetricsComponent {
+
+  public abstract Metrics metrics();
+
+  public abstract Metrics.Initializer metricsInitializer();
+
+  public static MetricsComponent get(Context context) {
+    return ((MetricsComponent.HasComponent)
+            ((HasRootComponent) context.getApplicationContext()).component())
+        .metricsComponent();
+  }
+
+  /** Used to refer to the root application component. */
+  public interface HasComponent {
+    MetricsComponent metricsComponent();
+  }
+}
diff --git a/java/com/android/dialer/metrics/StubMetrics.java b/java/com/android/dialer/metrics/StubMetrics.java
new file mode 100644
index 0000000..114eb43
--- /dev/null
+++ b/java/com/android/dialer/metrics/StubMetrics.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 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.dialer.metrics;
+
+import android.content.Context;
+import javax.inject.Inject;
+
+/** Stub {@link Metrics}. */
+public final class StubMetrics implements Metrics {
+
+  @Inject
+  StubMetrics() {}
+
+  @Override
+  public void startTimer(Context context, String timerEventName) {}
+
+  @Override
+  public void stopTimer(String timerEventName) {}
+
+  @Override
+  public void recordMemory(String memoryEventName) {}
+}
diff --git a/java/com/android/dialer/metrics/StubMetricsInitializer.java b/java/com/android/dialer/metrics/StubMetricsInitializer.java
new file mode 100644
index 0000000..cea4087
--- /dev/null
+++ b/java/com/android/dialer/metrics/StubMetricsInitializer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.dialer.metrics;
+
+import android.app.Application;
+import javax.inject.Inject;
+
+/** Stub for {@link Metrics.Initializer}. */
+public class StubMetricsInitializer implements Metrics.Initializer {
+
+  @Inject
+  StubMetricsInitializer() {}
+
+  @Override
+  public void initialize(Application application) {}
+}
diff --git a/java/com/android/dialer/metrics/StubMetricsModule.java b/java/com/android/dialer/metrics/StubMetricsModule.java
new file mode 100644
index 0000000..a2d9ebf
--- /dev/null
+++ b/java/com/android/dialer/metrics/StubMetricsModule.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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.dialer.metrics;
+
+import dagger.Binds;
+import dagger.Module;
+
+/** Binds stub {@link Metrics}. */
+@Module
+public interface StubMetricsModule {
+
+  @Binds
+  Metrics bindMetrics(StubMetrics stub);
+
+  @Binds
+  Metrics.Initializer bindMetricsInitializer(StubMetricsInitializer stub);
+}
diff --git a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
index ce4030d..9c54110 100644
--- a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
+++ b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
@@ -108,6 +108,28 @@
 
   /**
    * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method
+   * returns the photo thumbnail URI associated with that number.
+   *
+   * <p>If no photo thumbnail URI can be obtained from the {@link PhoneLookupInfo}, an empty string
+   * will be returned.
+   */
+  public String getPhotoThumbnailUri() {
+    switch (nameSource) {
+      case NameSource.CP2_LOCAL:
+        return Assert.isNotNull(firstCp2LocalContact).getPhotoThumbnailUri();
+      case NameSource.CP2_REMOTE:
+        return Assert.isNotNull(firstCp2RemoteContact).getPhotoThumbnailUri();
+      case NameSource.PEOPLE_API:
+      case NameSource.NONE:
+        return "";
+      default:
+        throw Assert.createUnsupportedOperationFailException(
+            String.format("Unsupported name source: %s", nameSource));
+    }
+  }
+
+  /**
+   * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method
    * returns the photo URI associated with that number.
    *
    * <p>If no photo URI can be obtained from the {@link PhoneLookupInfo}, an empty string will be
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java b/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java
index e392999..5a211ed 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java
@@ -35,12 +35,13 @@
       new String[] {
         Phone.DISPLAY_NAME_PRIMARY, // 0
         Phone.PHOTO_THUMBNAIL_URI, // 1
-        Phone.PHOTO_ID, // 2
-        Phone.TYPE, // 3
-        Phone.LABEL, // 4
-        Phone.NORMALIZED_NUMBER, // 5
-        Phone.CONTACT_ID, // 6
-        Phone.LOOKUP_KEY // 7
+        Phone.PHOTO_URI, // 2
+        Phone.PHOTO_ID, // 3
+        Phone.TYPE, // 4
+        Phone.LABEL, // 5
+        Phone.NORMALIZED_NUMBER, // 6
+        Phone.CONTACT_ID, // 7
+        Phone.LOOKUP_KEY // 8
       };
 
   // Projection for performing lookups using the PHONE_LOOKUP table
@@ -48,23 +49,25 @@
       new String[] {
         PhoneLookup.DISPLAY_NAME_PRIMARY, // 0
         PhoneLookup.PHOTO_THUMBNAIL_URI, // 1
-        PhoneLookup.PHOTO_ID, // 2
-        PhoneLookup.TYPE, // 3
-        PhoneLookup.LABEL, // 4
-        PhoneLookup.NORMALIZED_NUMBER, // 5
-        PhoneLookup.CONTACT_ID, // 6
-        PhoneLookup.LOOKUP_KEY // 7
+        PhoneLookup.PHOTO_URI, // 2
+        PhoneLookup.PHOTO_ID, // 3
+        PhoneLookup.TYPE, // 4
+        PhoneLookup.LABEL, // 5
+        PhoneLookup.NORMALIZED_NUMBER, // 6
+        PhoneLookup.CONTACT_ID, // 7
+        PhoneLookup.LOOKUP_KEY // 8
       };
 
   // The following indexes should match both PHONE_PROJECTION and PHONE_LOOKUP_PROJECTION above.
   private static final int CP2_INFO_NAME_INDEX = 0;
-  private static final int CP2_INFO_PHOTO_URI_INDEX = 1;
-  private static final int CP2_INFO_PHOTO_ID_INDEX = 2;
-  private static final int CP2_INFO_TYPE_INDEX = 3;
-  private static final int CP2_INFO_LABEL_INDEX = 4;
-  private static final int CP2_INFO_NORMALIZED_NUMBER_INDEX = 5;
-  private static final int CP2_INFO_CONTACT_ID_INDEX = 6;
-  private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7;
+  private static final int CP2_INFO_PHOTO_THUMBNAIL_URI_INDEX = 1;
+  private static final int CP2_INFO_PHOTO_URI_INDEX = 2;
+  private static final int CP2_INFO_PHOTO_ID_INDEX = 3;
+  private static final int CP2_INFO_TYPE_INDEX = 4;
+  private static final int CP2_INFO_LABEL_INDEX = 5;
+  private static final int CP2_INFO_NORMALIZED_NUMBER_INDEX = 6;
+  private static final int CP2_INFO_CONTACT_ID_INDEX = 7;
+  private static final int CP2_INFO_LOOKUP_KEY_INDEX = 8;
 
   private Cp2Projections() {}
 
@@ -82,6 +85,7 @@
    */
   static Cp2ContactInfo buildCp2ContactInfoFromCursor(Context appContext, Cursor cursor) {
     String displayName = cursor.getString(CP2_INFO_NAME_INDEX);
+    String photoThumbnailUri = cursor.getString(CP2_INFO_PHOTO_THUMBNAIL_URI_INDEX);
     String photoUri = cursor.getString(CP2_INFO_PHOTO_URI_INDEX);
     int photoId = cursor.getInt(CP2_INFO_PHOTO_ID_INDEX);
     int type = cursor.getInt(CP2_INFO_TYPE_INDEX);
@@ -93,6 +97,9 @@
     if (!TextUtils.isEmpty(displayName)) {
       infoBuilder.setName(displayName);
     }
+    if (!TextUtils.isEmpty(photoThumbnailUri)) {
+      infoBuilder.setPhotoThumbnailUri(photoThumbnailUri);
+    }
     if (!TextUtils.isEmpty(photoUri)) {
       infoBuilder.setPhotoUri(photoUri);
     }
diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
index e9cb9f8..dd6bf66 100644
--- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto
+++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
@@ -19,6 +19,7 @@
   message Cp2Info {
     // Information about a single contact, which can be a local contact or a
     // remote one.
+    // Next ID: 8
     message Cp2ContactInfo {
       // For a local contact:
       //   android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY
@@ -30,13 +31,19 @@
       //   android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI
       // For a remote contact:
       //   android.provider.ContactsContract.PhoneLookup.PHOTO_THUMBNAIL_URI
-      optional string photo_uri = 2;
+      optional string photo_thumbnail_uri = 2;
+
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_URI
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.PHOTO_URI
+      optional string photo_uri = 3;
 
       // For a local contact:
       //   android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_ID
       // For a remote contact:
       //   android.provider.ContactsContract.PhoneLookup.PHOTO_ID
-      optional fixed64 photo_id = 3;
+      optional fixed64 photo_id = 4;
 
       // For a local contact:
       //   android.provider.ContactsContract.CommonDataKinds.Phone.LABEL
@@ -44,13 +51,13 @@
       //   android.provider.ContactsContract.PhoneLookup.LABEL
       //
       // The value can be "Home", "Mobile", ect.
-      optional string label = 4;
+      optional string label = 5;
 
       // For a local contact:
       //   android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID
       // For a remote contact:
       //   android.provider.ContactsContract.PhoneLookup.CONTACT_ID
-      optional fixed64 contact_id = 5;
+      optional fixed64 contact_id = 6;
 
       // For a local contact:
       //   constructed based on
@@ -58,7 +65,7 @@
       // For a remote contact:
       //   constructed based on
       //   android.provider.ContactsContract.PhoneLookup.LOOKUP_KEY
-      optional string lookup_uri = 6;
+      optional string lookup_uri = 7;
     }
     // Repeated because one phone number can be associated with multiple CP2
     // contacts.
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index c62d40e..2d45457 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -488,7 +488,9 @@
     if (event.getAction() == MotionEvent.ACTION_UP) {
       v.performClick();
     }
-    FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onSearchListTouch();
+    if (event.getAction() == MotionEvent.ACTION_DOWN) {
+      FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onSearchListTouch();
+    }
     return false;
   }
 
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java
index 3c2526b..11a07d9 100644
--- a/java/com/android/dialer/simulator/Simulator.java
+++ b/java/com/android/dialer/simulator/Simulator.java
@@ -101,6 +101,9 @@
       MERGE,
       SEPARATE,
       SWAP,
+      START_RTT,
+      STOP_RTT,
+      HANDLE_RTT_UPGRADE_RESPONSE,
     })
     public @interface Type {}
 
@@ -118,6 +121,9 @@
     public static final int MERGE = 11;
     public static final int SEPARATE = 12;
     public static final int SWAP = 13;
+    public static final int START_RTT = 14;
+    public static final int STOP_RTT = 15;
+    public static final int HANDLE_RTT_UPGRADE_RESPONSE = 16;
 
     @Type public final int type;
     /** Holds event specific information. For example, for DTMF this could be the keycode. */
diff --git a/java/com/android/dialer/simulator/impl/RttChatBot.java b/java/com/android/dialer/simulator/impl/RttChatBot.java
new file mode 100644
index 0000000..9c2989a
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/RttChatBot.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 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.dialer.simulator.impl;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.support.annotation.MainThread;
+import android.telecom.Connection.RttTextStream;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.incallui.rtt.protocol.Constants;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/** Chat bot to generate remote RTT chat messages. */
+public class RttChatBot {
+
+  interface Callback {
+    void type(String text);
+  }
+
+  private static final int START_SENDING = 1;
+  private static final int SEND_MESSAGE = 2;
+
+  private static final String[] CANDIDATE_MESSAGES =
+      new String[] {
+        "To RTT or not to RTT, that is the question...",
+        "Making TTY great again!",
+        "I would be more comfortable with real \"Thyme\" chatting."
+            + " I don't know how to end this pun",
+        "お疲れ様でした",
+        "The FCC has mandated that I respond... I will do so begrudgingly",
+        "😂😂😂💯"
+      };
+
+  private final MessageHandler messageHandler;
+  private final HandlerThread handlerThread;
+
+  RttChatBot(RttTextStream rttTextStream) {
+    handlerThread = new HandlerThread("RttChatBot");
+    handlerThread.start();
+    messageHandler = new MessageHandler(handlerThread.getLooper(), rttTextStream);
+  }
+
+  @MainThread
+  public void start() {
+    Assert.isMainThread();
+    LogUtil.enterBlock("RttChatBot.start");
+    messageHandler.sendEmptyMessage(START_SENDING);
+  }
+
+  @MainThread
+  public void stop() {
+    Assert.isMainThread();
+    LogUtil.enterBlock("RttChatBot.stop");
+    if (handlerThread != null && handlerThread.isAlive()) {
+      handlerThread.quit();
+    }
+  }
+
+  private static class MessageHandler extends Handler {
+    private final RttTextStream rttTextStream;
+    private final Random random = new Random();
+    private final List<String> messageQueue = new ArrayList<>();
+    private int currentTypingPosition = -1;
+    private String currentTypingMessage = null;
+
+    MessageHandler(Looper looper, RttTextStream rttTextStream) {
+      super(looper);
+      this.rttTextStream = rttTextStream;
+    }
+
+    @Override
+    public void handleMessage(android.os.Message msg) {
+      switch (msg.what) {
+        case START_SENDING:
+          sendMessage(obtainMessage(SEND_MESSAGE, nextTyping()));
+          break;
+        case SEND_MESSAGE:
+          String message = (String) msg.obj;
+          LogUtil.w("test", "type: %s, to stream: %s", message, rttTextStream);
+          try {
+            rttTextStream.write(message);
+          } catch (IOException e) {
+            LogUtil.e("RttChatBot.MessageHandler", "write message", e);
+          }
+          if (Constants.BUBBLE_BREAKER.equals(message)) {
+            // Wait 1-11s between two messages.
+            sendMessageDelayed(
+                obtainMessage(SEND_MESSAGE, nextTyping()), 1000 * (1 + random.nextInt(10)));
+          } else {
+            // Wait up to 2s between typing.
+            sendMessageDelayed(obtainMessage(SEND_MESSAGE, nextTyping()), 200 * random.nextInt(10));
+          }
+          break;
+        default: // fall out
+      }
+    }
+
+    private String nextTyping() {
+      if (currentTypingPosition < 0 || currentTypingMessage == null) {
+        if (messageQueue.isEmpty()) {
+          String text = CANDIDATE_MESSAGES[random.nextInt(CANDIDATE_MESSAGES.length)];
+          messageQueue.add(text);
+        }
+        currentTypingMessage = messageQueue.remove(0);
+        currentTypingPosition = 0;
+      }
+      if (currentTypingPosition < currentTypingMessage.length()) {
+        int size = random.nextInt(currentTypingMessage.length() - currentTypingPosition + 1);
+        String messageToType =
+            currentTypingMessage.substring(currentTypingPosition, currentTypingPosition + size);
+        currentTypingPosition = currentTypingPosition + size;
+        return messageToType;
+      } else {
+        currentTypingPosition = -1;
+        currentTypingMessage = null;
+        return Constants.BUBBLE_BREAKER;
+      }
+    }
+  }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
index 2bfa982..81a3d30 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConferenceCreator.java
@@ -97,7 +97,8 @@
       default:
         break;
     }
-    SimulatorSimCallManager.addNewIncomingCall(context, number, false /* isVideo */, extras);
+    SimulatorSimCallManager.addNewIncomingCall(
+        context, number, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
   }
 
   @Override
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index d7427dd..c832a50 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -121,6 +121,24 @@
     onEvent(new Event(Event.DTMF, Character.toString(c), null));
   }
 
+  @Override
+  public void onStartRtt(@NonNull RttTextStream rttTextStream) {
+    LogUtil.enterBlock("SimulatorConnection.onStartRtt");
+    onEvent(new Event(Event.START_RTT));
+  }
+
+  @Override
+  public void onStopRtt() {
+    LogUtil.enterBlock("SimulatorConnection.onStopRtt");
+    onEvent(new Event(Event.STOP_RTT));
+  }
+
+  @Override
+  public void handleRttUpgradeResponse(RttTextStream rttTextStream) {
+    LogUtil.enterBlock("SimulatorConnection.handleRttUpgradeResponse");
+    onEvent(new Event(Event.HANDLE_RTT_UPGRADE_RESPONSE));
+  }
+
   void onEvent(@NonNull Event event) {
     events.add(Assert.isNotNull(event));
     for (Listener listener : new ArrayList<>(listeners)) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
index 0bd1c0f..1bf4b2a 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
@@ -33,7 +33,6 @@
 import com.android.dialer.persistentlog.PersistentLogger;
 import com.android.dialer.preferredsim.PreferredSimFallbackContract;
 import com.android.dialer.simulator.SimulatorComponent;
-import com.android.incallui.rtt.impl.RttChatActivity;
 
 /** Implements the top level simulator menu. */
 final class SimulatorMainMenu {
@@ -42,9 +41,9 @@
     SimulatorSubMenu simulatorSubMenu = new SimulatorSubMenu(activity.getApplicationContext());
     simulatorSubMenu
         .addItem("Voice call", SimulatorVoiceCall.getActionProvider(activity))
+        .addItem("Rtt call", SimulatorRttCall.getActionProvider(activity.getApplicationContext()))
         .addItem(
             "IMS video", SimulatorVideoCall.getActionProvider(activity.getApplicationContext()))
-        .addItem("Rtt call mock", () -> simulateRttCallMock(activity.getApplicationContext()))
         .addItem(
             "Notifications",
             SimulatorNotifications.getActionProvider(activity.getApplicationContext()))
@@ -79,10 +78,6 @@
     return simulatorSubMenu;
   }
 
-  private static void simulateRttCallMock(@NonNull Context context) {
-    context.startActivity(new Intent(context, RttChatActivity.class));
-  }
-
   private static void populateDatabase(@NonNull Context context) {
     DialerExecutorComponent.get(context)
         .dialerExecutorFactory()
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
index 6d4a262..b855615 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
@@ -78,7 +78,8 @@
     extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
     extras.putBoolean(EXTRA_IS_MISSED_CALL_CONNECTION, true);
 
-    SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras);
+    SimulatorSimCallManager.addNewIncomingCall(
+        context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
   }
 
   private static boolean isMissedCallConnection(@NonNull Connection connection) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorRttCall.java b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java
new file mode 100644
index 0000000..7b00667
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorRttCall.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.view.ActionProvider;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.Simulator;
+import com.android.dialer.simulator.Simulator.Event;
+
+/** Entry point in the simulator to create voice calls. */
+final class SimulatorRttCall
+    implements SimulatorConnectionService.Listener, SimulatorConnection.Listener {
+
+  @NonNull private final Context context;
+  @Nullable private String connectionTag;
+
+  static ActionProvider getActionProvider(@NonNull Context context) {
+    return new SimulatorSubMenu(context)
+        .addItem("Incoming call", () -> new SimulatorRttCall(context).addNewIncomingCall(false))
+        .addItem("Outgoing call", () -> new SimulatorRttCall(context).addNewOutgoingCall())
+        .addItem("Emergency call", () -> new SimulatorRttCall(context).addNewEmergencyCall());
+  }
+
+  private SimulatorRttCall(@NonNull Context context) {
+    this.context = Assert.isNotNull(context);
+    SimulatorConnectionService.addListener(this);
+    SimulatorConnectionService.addListener(
+        new SimulatorConferenceCreator(context, Simulator.CONFERENCE_TYPE_GSM));
+  }
+
+  private void addNewIncomingCall(boolean isSpam) {
+    String callerId =
+        isSpam
+            ? "+1-661-778-3020" /* Blacklisted custom spam number */
+            : "+44 (0) 20 7031 3000" /* Google London office */;
+    connectionTag =
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_RTT);
+  }
+
+  private void addNewOutgoingCall() {
+    String callerId = "+55-31-2128-6800"; // Brazil office.
+    connectionTag =
+        SimulatorSimCallManager.addNewOutgoingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_RTT);
+  }
+
+  private void addNewEmergencyCall() {
+    String callerId = "911";
+    connectionTag =
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_RTT);
+  }
+
+  @Override
+  public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {
+    if (isMyConnection(connection)) {
+      LogUtil.i("SimulatorRttCall.onNewOutgoingConnection", "connection created");
+      handleNewConnection(connection);
+
+      // Telecom will force the connection to switch to Dialing when we return it. Wait until after
+      // we're returned it before changing call state.
+      ThreadUtil.postOnUiThread(connection::setActive);
+    }
+  }
+
+  @Override
+  public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
+    if (isMyConnection(connection)) {
+      LogUtil.i("SimulatorRttCall.onNewIncomingConnection", "connection created");
+      handleNewConnection(connection);
+    }
+  }
+
+  @Override
+  public void onConference(
+      @NonNull SimulatorConnection connection1, @NonNull SimulatorConnection connection2) {}
+
+  private void handleNewConnection(@NonNull SimulatorConnection connection) {
+    connection.addListener(this);
+    connection.setConnectionProperties(
+        connection.getConnectionProperties() | Connection.PROPERTY_IS_RTT);
+  }
+
+  private boolean isMyConnection(@NonNull Connection connection) {
+    return connection.getExtras().getBoolean(connectionTag);
+  }
+
+  @Override
+  public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
+    switch (event.type) {
+      case Event.NONE:
+        throw Assert.createIllegalStateFailException();
+      case Event.ANSWER:
+        connection.setActive();
+        break;
+      case Event.REJECT:
+        connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+        break;
+      case Event.HOLD:
+        connection.setOnHold();
+        break;
+      case Event.UNHOLD:
+        connection.setActive();
+        break;
+      case Event.DISCONNECT:
+        connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+        break;
+      case Event.SESSION_MODIFY_REQUEST:
+        ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
+        break;
+      default:
+        LogUtil.i("SimulatorRttCall.onEvent", "unexpected event: " + event.type);
+        break;
+    }
+  }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
index f28393c..d51e068 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -30,6 +31,8 @@
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.strictmode.StrictModeUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
@@ -46,10 +49,20 @@
  */
 public class SimulatorSimCallManager {
 
+  public static final int CALL_TYPE_VOICE = 1;
+  public static final int CALL_TYPE_VIDEO = 2;
+  public static final int CALL_TYPE_RTT = 3;
+
+  /** Call type of a simulator call. */
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef({CALL_TYPE_VOICE, CALL_TYPE_VIDEO, CALL_TYPE_RTT})
+  public @interface CallType {}
+
   private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
   private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID";
   private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
   private static final String EXTRA_CONNECTION_TAG = "connection_tag";
+  private static final String EXTRA_CONNECTION_CALL_TYPE = "connection_call_type";
 
   static void register(@NonNull Context context) {
     LogUtil.enterBlock("SimulatorSimCallManager.register");
@@ -75,15 +88,15 @@
 
   @NonNull
   public static String addNewOutgoingCall(
-      @NonNull Context context, @NonNull String phoneNumber, boolean isVideo) {
-    return addNewOutgoingCall(context, phoneNumber, isVideo, new Bundle());
+      @NonNull Context context, @NonNull String phoneNumber, @CallType int callType) {
+    return addNewOutgoingCall(context, phoneNumber, callType, new Bundle());
   }
 
   @NonNull
   public static String addNewOutgoingCall(
       @NonNull Context context,
       @NonNull String phoneNumber,
-      boolean isVideo,
+      @CallType int callType,
       @NonNull Bundle extras) {
     LogUtil.enterBlock("SimulatorSimCallManager.addNewOutgoingCall");
     Assert.isNotNull(context);
@@ -94,13 +107,18 @@
     register(context);
 
     extras = new Bundle(extras);
-    extras.putAll(createSimulatorConnectionExtras());
+    extras.putAll(createSimulatorConnectionExtras(callType));
 
     Bundle outgoingCallExtras = new Bundle();
     outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
     outgoingCallExtras.putParcelable(
         TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
-        isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context));
+        callType == CALL_TYPE_VIDEO
+            ? getVideoProviderHandle(context)
+            : getSystemPhoneAccountHandle(context));
+    if (callType == CALL_TYPE_RTT) {
+      outgoingCallExtras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
+    }
 
     TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
     try {
@@ -114,13 +132,16 @@
 
   @NonNull
   public static String addNewIncomingCall(
-      @NonNull Context context, @NonNull String callerId, boolean isVideo) {
-    return addNewIncomingCall(context, callerId, isVideo, new Bundle());
+      @NonNull Context context, @NonNull String callerId, @CallType int callType) {
+    return addNewIncomingCall(context, callerId, callType, new Bundle());
   }
 
   @NonNull
   public static String addNewIncomingCall(
-      @NonNull Context context, @NonNull String callerId, boolean isVideo, @NonNull Bundle extras) {
+      @NonNull Context context,
+      @NonNull String callerId,
+      @CallType int callType,
+      @NonNull Bundle extras) {
     LogUtil.enterBlock("SimulatorSimCallManager.addNewIncomingCall");
     Assert.isNotNull(context);
     Assert.isNotNull(callerId);
@@ -130,18 +151,21 @@
 
     extras = new Bundle(extras);
     extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId);
-    extras.putAll(createSimulatorConnectionExtras());
+    extras.putAll(createSimulatorConnectionExtras(callType));
 
     TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
     telecomManager.addNewIncomingCall(
-        isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context), extras);
+        callType == CALL_TYPE_VIDEO
+            ? getVideoProviderHandle(context)
+            : getSystemPhoneAccountHandle(context),
+        extras);
     return extras.getString(EXTRA_CONNECTION_TAG);
   }
 
   @NonNull
   private static PhoneAccount buildSimCallManagerAccount(Context context) {
     return new PhoneAccount.Builder(getSimCallManagerHandle(context), "Simulator SIM call manager")
-        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER | PhoneAccount.CAPABILITY_RTT)
         .setShortDescription("Simulator SIM call manager")
         .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
         .build();
@@ -218,12 +242,16 @@
   }
 
   @NonNull
-  static Bundle createSimulatorConnectionExtras() {
+  static Bundle createSimulatorConnectionExtras(@CallType int callType) {
     Bundle extras = new Bundle();
     extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
     String connectionTag = createUniqueConnectionTag();
     extras.putString(EXTRA_CONNECTION_TAG, connectionTag);
     extras.putBoolean(connectionTag, true);
+    extras.putInt(EXTRA_CONNECTION_CALL_TYPE, callType);
+    if (callType == CALL_TYPE_RTT) {
+      extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
+    }
     return extras;
   }
 
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
index f7256a1..0bb56f1 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
@@ -77,7 +77,8 @@
     }
     String callerId = "+44 (0) 20 7031 3000"; // Google London office
     connectionTag =
-        SimulatorSimCallManager.addNewIncomingCall(context, callerId, true /* isVideo */);
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_VIDEO);
   }
 
   private void addNewOutgoingCall() {
@@ -87,7 +88,8 @@
     }
     String phoneNumber = "+44 (0) 20 7031 3000"; // Google London office
     connectionTag =
-        SimulatorSimCallManager.addNewOutgoingCall(context, phoneNumber, true /* isVideo */);
+        SimulatorSimCallManager.addNewOutgoingCall(
+            context, phoneNumber, SimulatorSimCallManager.CALL_TYPE_VIDEO);
   }
 
   @Override
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index 67a2db8..d4c7ee4 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -104,7 +104,10 @@
               extras.putBoolean(Simulator.IS_ENRICHED_CALL, true);
               connectionTag =
                   SimulatorSimCallManager.addNewIncomingCall(
-                      context, Simulator.ENRICHED_CALL_INCOMING_NUMBER, false, extras);
+                      context,
+                      Simulator.ENRICHED_CALL_INCOMING_NUMBER,
+                      SimulatorSimCallManager.CALL_TYPE_VOICE,
+                      extras);
             },
             DialerExecutorComponent.get(context).uiExecutor());
   }
@@ -119,7 +122,10 @@
               extras.putBoolean(Simulator.IS_ENRICHED_CALL, true);
               connectionTag =
                   SimulatorSimCallManager.addNewOutgoingCall(
-                      context, Simulator.ENRICHED_CALL_OUTGOING_NUMBER, false, extras);
+                      context,
+                      Simulator.ENRICHED_CALL_OUTGOING_NUMBER,
+                      SimulatorSimCallManager.CALL_TYPE_VOICE,
+                      extras);
             },
             DialerExecutorComponent.get(context).uiExecutor());
   }
@@ -127,7 +133,8 @@
   private void addNewIncomingCall() {
     String callerId = "+44 (0) 20 7031 3000" /* Google London office */;
     connectionTag =
-        SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */);
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE);
   }
 
   private void addNewIncomingCall(AppCompatActivity activity) {
@@ -137,7 +144,7 @@
               extras.putInt(Simulator.PRESENTATION_CHOICE, callerIdPresentation);
               connectionTag =
                   SimulatorSimCallManager.addNewIncomingCall(
-                      context, callerId, false /* isVideo */, extras);
+                      context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
             })
         .show(activity.getSupportFragmentManager(), "SimulatorDialog");
   }
@@ -145,7 +152,8 @@
   private void addNewOutgoingCall() {
     String callerId = "+55-31-2128-6800"; // Brazil office.
     connectionTag =
-        SimulatorSimCallManager.addNewOutgoingCall(context, callerId, false /* isVideo */);
+        SimulatorSimCallManager.addNewOutgoingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE);
   }
 
   private void addNewOutgoingCall(AppCompatActivity activity) {
@@ -155,7 +163,7 @@
               extras.putInt(Simulator.PRESENTATION_CHOICE, callerIdPresentation);
               connectionTag =
                   SimulatorSimCallManager.addNewOutgoingCall(
-                      context, callerId, false /* isVideo */, extras);
+                      context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE, extras);
             })
         .show(activity.getSupportFragmentManager(), "SimulatorDialog");
   }
@@ -163,12 +171,15 @@
   private void addSpamIncomingCall() {
     String callerId = "+1-661-778-3020"; /* Blacklisted custom spam number */
     connectionTag =
-        SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */);
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE);
   }
 
   private void addNewEmergencyCallBack() {
     String callerId = "911";
-    connectionTag = SimulatorSimCallManager.addNewIncomingCall(context, callerId, false);
+    connectionTag =
+        SimulatorSimCallManager.addNewIncomingCall(
+            context, callerId, SimulatorSimCallManager.CALL_TYPE_VOICE);
   }
 
   @Override
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 8769be5..67f5cfe 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -83,6 +83,10 @@
 import com.android.incallui.incall.protocol.InCallScreenDelegate;
 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
 import com.android.incallui.incalluilock.InCallUiLock;
+import com.android.incallui.rtt.bindings.RttBindings;
+import com.android.incallui.rtt.protocol.RttCallScreen;
+import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
+import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
 import com.android.incallui.video.bindings.VideoBindings;
 import com.android.incallui.video.protocol.VideoCallScreen;
@@ -100,6 +104,7 @@
         InCallScreenDelegateFactory,
         InCallButtonUiDelegateFactory,
         VideoCallScreenDelegateFactory,
+        RttCallScreenDelegateFactory,
         PseudoScreenState.StateChangedListener {
 
   @Retention(RetentionPolicy.SOURCE)
@@ -136,6 +141,7 @@
   private boolean didShowAnswerScreen;
   private boolean didShowInCallScreen;
   private boolean didShowVideoCallScreen;
+  private boolean didShowRttCallScreen;
   private boolean dismissKeyguard;
   private boolean isInShowMainInCallFragment;
   private boolean isRecreating; // whether the activity is going to be recreated
@@ -1220,37 +1226,47 @@
     isInShowMainInCallFragment = true;
     ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
     ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
+    ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi();
     LogUtil.i(
         "InCallActivity.showMainInCallFragment",
-        "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
-            + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
+        "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b "
+            + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, "
+            + "didShowVideoCallScreen: %b",
         shouldShowAnswerUi.shouldShow,
+        shouldShowRttUi.shouldShow,
         shouldShowVideoUi.shouldShow,
         didShowAnswerScreen,
         didShowInCallScreen,
+        didShowRttCallScreen,
         didShowVideoCallScreen);
     // Only video call ui allows orientation change.
     setAllowOrientationChange(shouldShowVideoUi.shouldShow);
 
     FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-    boolean didChangeInCall;
-    boolean didChangeVideo;
-    boolean didChangeAnswer;
+    boolean didChange;
     if (shouldShowAnswerUi.shouldShow) {
-      didChangeInCall = hideInCallScreenFragment(transaction);
-      didChangeVideo = hideVideoCallScreenFragment(transaction);
-      didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
+      didChange = hideInCallScreenFragment(transaction);
+      didChange |= hideVideoCallScreenFragment(transaction);
+      didChange |= hideRttCallScreenFragment(transaction);
+      didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
     } else if (shouldShowVideoUi.shouldShow) {
-      didChangeInCall = hideInCallScreenFragment(transaction);
-      didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
-      didChangeAnswer = hideAnswerScreenFragment(transaction);
+      didChange = hideInCallScreenFragment(transaction);
+      didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
+      didChange |= hideRttCallScreenFragment(transaction);
+      didChange |= hideAnswerScreenFragment(transaction);
+    } else if (shouldShowRttUi.shouldShow) {
+      didChange = hideInCallScreenFragment(transaction);
+      didChange |= hideVideoCallScreenFragment(transaction);
+      didChange |= hideAnswerScreenFragment(transaction);
+      didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call);
     } else {
-      didChangeInCall = showInCallScreenFragment(transaction);
-      didChangeVideo = hideVideoCallScreenFragment(transaction);
-      didChangeAnswer = hideAnswerScreenFragment(transaction);
+      didChange = showInCallScreenFragment(transaction);
+      didChange |= hideVideoCallScreenFragment(transaction);
+      didChange |= hideRttCallScreenFragment(transaction);
+      didChange |= hideAnswerScreenFragment(transaction);
     }
 
-    if (didChangeInCall || didChangeVideo || didChangeAnswer) {
+    if (didChange) {
       Trace.beginSection("InCallActivity.commitTransaction");
       transaction.commitNow();
       Trace.endSection();
@@ -1308,6 +1324,26 @@
     return new ShouldShowUiResult(false, null);
   }
 
+  private static ShouldShowUiResult getShouldShowRttUi() {
+    DialerCall call = CallList.getInstance().getFirstCall();
+    if (call == null) {
+      LogUtil.i("InCallActivity.getShouldShowRttUi", "null call");
+      return new ShouldShowUiResult(false, null);
+    }
+
+    if (call.isRttCall()) {
+      LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
+      return new ShouldShowUiResult(true, call);
+    }
+
+    if (call.hasSentRttUpgradeRequest()) {
+      LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt");
+      return new ShouldShowUiResult(true, call);
+    }
+
+    return new ShouldShowUiResult(false, null);
+  }
+
   private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
     // When rejecting a call the active call can become null in which case we should continue
     // showing the answer screen.
@@ -1347,6 +1383,7 @@
     AnswerScreen answerScreen =
         AnswerBindings.createAnswerScreen(
             call.getId(),
+            call.isRttCall(),
             call.isVideoCall(),
             isVideoUpgradeRequest,
             call.getVideoTech().isSelfManagedCamera(),
@@ -1418,6 +1455,33 @@
     return true;
   }
 
+  private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
+    if (didShowRttCallScreen) {
+      // This shouldn't happen since only one RTT call is allow at same time.
+      if (!getRttCallScreen().getCallId().equals(call.getId())) {
+        LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match");
+      }
+      return false;
+    }
+    RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId());
+    transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN);
+    Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
+    didShowRttCallScreen = true;
+    return true;
+  }
+
+  private boolean hideRttCallScreenFragment(FragmentTransaction transaction) {
+    if (!didShowRttCallScreen) {
+      return false;
+    }
+    RttCallScreen rttCallScreen = getRttCallScreen();
+    if (rttCallScreen != null) {
+      transaction.remove(rttCallScreen.getRttCallScreenFragment());
+    }
+    didShowRttCallScreen = false;
+    return true;
+  }
+
   private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
     if (didShowVideoCallScreen) {
       VideoCallScreen videoCallScreen = getVideoCallScreen();
@@ -1467,6 +1531,10 @@
     return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
   }
 
+  private RttCallScreen getRttCallScreen() {
+    return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN);
+  }
+
   @Override
   public void onPseudoScreenStateChanged(boolean isOn) {
     LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
@@ -1499,6 +1567,11 @@
     return super.dispatchTouchEvent(event);
   }
 
+  @Override
+  public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) {
+    return new RttCallPresenter();
+  }
+
   private static class ShouldShowUiResult {
     public final boolean shouldShow;
     public final DialerCall call;
@@ -1536,6 +1609,7 @@
     static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
     static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
     static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
+    static final String RTT_CALL_SCREEN = "tag_rtt_call_screen";
     static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
   }
 
diff --git a/java/com/android/incallui/ProximitySensor.java b/java/com/android/incallui/ProximitySensor.java
index f82b75d..4b03344 100644
--- a/java/com/android/incallui/ProximitySensor.java
+++ b/java/com/android/incallui/ProximitySensor.java
@@ -55,6 +55,7 @@
   private boolean dialpadVisible;
   private boolean isAttemptingVideoCall;
   private boolean isVideoCall;
+  private boolean isRttCall;
 
   public ProximitySensor(
       @NonNull Context context,
@@ -112,10 +113,14 @@
 
     DialerCall activeCall = callList.getActiveCall();
     boolean isVideoCall = activeCall != null && activeCall.isVideoCall();
+    boolean isRttCall = activeCall != null && activeCall.isRttCall();
 
-    if (isOffhook != isPhoneOffhook || this.isVideoCall != isVideoCall) {
+    if (isOffhook != isPhoneOffhook
+        || this.isVideoCall != isVideoCall
+        || this.isRttCall != isRttCall) {
       isPhoneOffhook = isOffhook;
       this.isVideoCall = isVideoCall;
+      this.isRttCall = isRttCall;
 
       orientation = AccelerometerListener.ORIENTATION_UNKNOWN;
       accelerometerListener.enable(isPhoneOffhook);
@@ -217,7 +222,8 @@
             || CallAudioState.ROUTE_SPEAKER == audioRoute
             || CallAudioState.ROUTE_BLUETOOTH == audioRoute
             || isAttemptingVideoCall
-            || isVideoCall);
+            || isVideoCall
+            || isRttCall);
 
     // We do not keep the screen off when the user is outside in-call screen and we are
     // horizontal, but we do not force it on when we become horizontal until the
diff --git a/java/com/android/incallui/RttCallPresenter.java b/java/com/android/incallui/RttCallPresenter.java
new file mode 100644
index 0000000..b90d56b
--- /dev/null
+++ b/java/com/android/incallui/RttCallPresenter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.incallui;
+
+import android.content.Context;
+import com.android.incallui.rtt.protocol.RttCallScreen;
+import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
+
+/**
+ * Logic related to the {@link RttCallScreen} and for managing changes to the RTT calling surfaces
+ * based on other user interface events and incoming events.
+ */
+public class RttCallPresenter implements RttCallScreenDelegate {
+
+  private Context appContext;
+  private RttCallScreen rttCallScreen;
+
+  @Override
+  public void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen) {
+    this.appContext = context.getApplicationContext();
+    this.rttCallScreen = rttCallScreen;
+  }
+
+  @Override
+  public void onRttCallScreenUiReady() {}
+
+  @Override
+  public void onRttCallScreenUiUnready() {}
+}
diff --git a/java/com/android/incallui/answer/bindings/AnswerBindings.java b/java/com/android/incallui/answer/bindings/AnswerBindings.java
index 0b546db..9f4199b 100644
--- a/java/com/android/incallui/answer/bindings/AnswerBindings.java
+++ b/java/com/android/incallui/answer/bindings/AnswerBindings.java
@@ -24,6 +24,7 @@
 
   public static AnswerScreen createAnswerScreen(
       String callId,
+      boolean isRttCall,
       boolean isVideoCall,
       boolean isVideoUpgradeRequest,
       boolean isSelfManagedCamera,
@@ -31,6 +32,7 @@
       boolean hasCallOnHold) {
     return AnswerFragment.newInstance(
         callId,
+        isRttCall,
         isVideoCall,
         isVideoUpgradeRequest,
         isSelfManagedCamera,
diff --git a/java/com/android/incallui/answer/impl/AnswerFragment.java b/java/com/android/incallui/answer/impl/AnswerFragment.java
index d687b6e..8626e6d 100644
--- a/java/com/android/incallui/answer/impl/AnswerFragment.java
+++ b/java/com/android/incallui/answer/impl/AnswerFragment.java
@@ -103,6 +103,8 @@
   @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
   static final String ARG_CALL_ID = "call_id";
 
+  static final String ARG_IS_RTT_CALL = "is_rtt_call";
+
   @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
   static final String ARG_IS_VIDEO_CALL = "is_video_call";
 
@@ -344,6 +346,7 @@
 
   public static AnswerFragment newInstance(
       String callId,
+      boolean isRttCall,
       boolean isVideoCall,
       boolean isVideoUpgradeRequest,
       boolean isSelfManagedCamera,
@@ -351,6 +354,7 @@
       boolean hasCallOnHold) {
     Bundle bundle = new Bundle();
     bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId));
+    bundle.putBoolean(ARG_IS_RTT_CALL, isRttCall);
     bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall);
     bundle.putBoolean(ARG_IS_VIDEO_UPGRADE_REQUEST, isVideoUpgradeRequest);
     bundle.putBoolean(ARG_IS_SELF_MANAGED_CAMERA, isSelfManagedCamera);
@@ -663,6 +667,7 @@
     Trace.beginSection("AnswerFragment.onCreateView");
     Bundle arguments = getArguments();
     Assert.checkState(arguments.containsKey(ARG_CALL_ID));
+    Assert.checkState(arguments.containsKey(ARG_IS_RTT_CALL));
     Assert.checkState(arguments.containsKey(ARG_IS_VIDEO_CALL));
     Assert.checkState(arguments.containsKey(ARG_IS_VIDEO_UPGRADE_REQUEST));
 
@@ -836,6 +841,11 @@
   }
 
   @Override
+  public boolean isRttCall() {
+    return getArguments().getBoolean(ARG_IS_RTT_CALL);
+  }
+
+  @Override
   public boolean isVideoCall() {
     return getArguments().getBoolean(ARG_IS_VIDEO_CALL);
   }
diff --git a/java/com/android/incallui/answer/impl/answermethod/AnswerMethodHolder.java b/java/com/android/incallui/answer/impl/answermethod/AnswerMethodHolder.java
index afa194f..0f1455c 100644
--- a/java/com/android/incallui/answer/impl/answermethod/AnswerMethodHolder.java
+++ b/java/com/android/incallui/answer/impl/answermethod/AnswerMethodHolder.java
@@ -46,4 +46,6 @@
   boolean isVideoCall();
 
   boolean isVideoUpgradeRequest();
+
+  boolean isRttCall();
 }
diff --git a/java/com/android/incallui/answer/impl/answermethod/FlingUpDownMethod.java b/java/com/android/incallui/answer/impl/answermethod/FlingUpDownMethod.java
index fe6bbbc..b5dbc0c 100644
--- a/java/com/android/incallui/answer/impl/answermethod/FlingUpDownMethod.java
+++ b/java/com/android/incallui/answer/impl/answermethod/FlingUpDownMethod.java
@@ -335,6 +335,8 @@
     }
     if (getParent().isVideoCall() || getParent().isVideoUpgradeRequest()) {
       contactPuckIcon.setImageResource(R.drawable.quantum_ic_videocam_white_24);
+    } else if (getParent().isRttCall()) {
+      contactPuckIcon.setImageResource(R.drawable.quantum_ic_call_white_24);
     } else {
       contactPuckIcon.setImageResource(R.drawable.quantum_ic_call_white_24);
     }
diff --git a/java/com/android/incallui/answer/protocol/AnswerScreen.java b/java/com/android/incallui/answer/protocol/AnswerScreen.java
index 5ad5002..f030ce9 100644
--- a/java/com/android/incallui/answer/protocol/AnswerScreen.java
+++ b/java/com/android/incallui/answer/protocol/AnswerScreen.java
@@ -24,6 +24,8 @@
 
   String getCallId();
 
+  boolean isRttCall();
+
   boolean isVideoCall();
 
   boolean isVideoUpgradeRequest();
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 1785ece..cbe7c57 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -32,6 +32,7 @@
 import android.support.v4.os.BuildCompat;
 import android.telecom.Call;
 import android.telecom.Call.Details;
+import android.telecom.Call.RttCall;
 import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
@@ -263,6 +264,28 @@
         }
 
         @Override
+        public void onRttModeChanged(Call call, int mode) {
+          LogUtil.v("TelecomCallCallback.onRttModeChanged", "mode=%d", mode);
+        }
+
+        @Override
+        public void onRttRequest(Call call, int id) {
+          LogUtil.v("TelecomCallCallback.onRttRequest", "id=%d", id);
+        }
+
+        @Override
+        public void onRttInitiationFailure(Call call, int reason) {
+          LogUtil.v("TelecomCallCallback.onRttInitiationFailure", "reason=%d", reason);
+          update();
+        }
+
+        @Override
+        public void onRttStatusChanged(Call call, boolean enabled, RttCall rttCall) {
+          LogUtil.v("TelecomCallCallback.onRttStatusChanged", "enabled=%b", enabled);
+          update();
+        }
+
+        @Override
         public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
           LogUtil.v(
               "TelecomCallCallback.onConnectionEvent",
@@ -906,6 +929,14 @@
     return getVideoTech().isTransmittingOrReceiving() || VideoProfile.isVideo(getVideoState());
   }
 
+  public boolean isRttCall() {
+    if (BuildCompat.isAtLeastP()) {
+      return getTelecomCall().isRttActive();
+    } else {
+      return false;
+    }
+  }
+
   public boolean hasReceivedVideoUpgradeRequest() {
     return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
   }
@@ -914,6 +945,11 @@
     return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
   }
 
+  public boolean hasSentRttUpgradeRequest() {
+    // TODO(wangqi): Implement this.
+    return false;
+  }
+
   /**
    * Determines if the call handle is an emergency number or not and caches the result to avoid
    * repeated calls to isEmergencyNumber.
diff --git a/java/com/android/incallui/rtt/bindings/RttBindings.java b/java/com/android/incallui/rtt/bindings/RttBindings.java
new file mode 100644
index 0000000..8f9a143
--- /dev/null
+++ b/java/com/android/incallui/rtt/bindings/RttBindings.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 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.incallui.rtt.bindings;
+
+import com.android.incallui.rtt.impl.RttChatFragment;
+import com.android.incallui.rtt.protocol.RttCallScreen;
+
+/** Bindings for RTT module. */
+public class RttBindings {
+
+  public static RttCallScreen createRttCallScreen(String callId) {
+    return RttChatFragment.newInstance(callId);
+  }
+}
diff --git a/java/com/android/incallui/rtt/impl/AndroidManifest.xml b/java/com/android/incallui/rtt/impl/AndroidManifest.xml
index fc0705d..7f58f71 100644
--- a/java/com/android/incallui/rtt/impl/AndroidManifest.xml
+++ b/java/com/android/incallui/rtt/impl/AndroidManifest.xml
@@ -13,14 +13,5 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<manifest
-    package="com.android.incallui.rtt.impl"
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <application android:theme="@style/Theme.AppCompat">
-  <activity
-      android:name=".RttChatActivity"
-      android:exported="false"
-      android:theme="@style/DialerThemeBase.NoActionBar"
-      android:windowSoftInputMode="adjustResize"/>
-  </application>
+<manifest package="com.android.incallui.rtt.impl">
 </manifest>
diff --git a/java/com/android/incallui/rtt/impl/RttChatActivity.java b/java/com/android/incallui/rtt/impl/RttChatActivity.java
deleted file mode 100644
index 96056f7..0000000
--- a/java/com/android/incallui/rtt/impl/RttChatActivity.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2018 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.incallui.rtt.impl;
-
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-import android.view.View;
-
-/** Activity to for RTT chat window. */
-public class RttChatActivity extends FragmentActivity {
-
-  @Override
-  protected void onCreate(@Nullable Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    setContentView(R.layout.activity_rtt);
-    getSupportFragmentManager()
-        .beginTransaction()
-        .add(
-            R.id.fragment_rtt,
-            RttChatFragment.newInstance("", "Jane Williamson", SystemClock.elapsedRealtime()))
-        .commit();
-    getWindow().setStatusBarColor(getColor(R.color.rtt_status_bar_color));
-    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
-  }
-}
diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
index 1db4c6b..1ea7f31 100644
--- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java
+++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
@@ -17,19 +17,15 @@
 package com.android.incallui.rtt.impl;
 
 import android.content.Context;
-import android.support.annotation.MainThread;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.ThreadUtil;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Random;
 
 /** Adapter class for holding RTT chat data. */
 public class RttChatAdapter extends RecyclerView.Adapter<RttChatMessageViewHolder> {
@@ -42,13 +38,11 @@
   private final List<RttChatMessage> rttMessages = new ArrayList<>();
   private int lastIndexOfLocalMessage = -1;
   private int lastIndexOfRemoteMessage = -1;
-  private final TypeBot typeBot;
   private final MessageListener messageListener;
 
   RttChatAdapter(Context context, MessageListener listener) {
     this.context = context;
     this.messageListener = listener;
-    typeBot = new TypeBot(text -> ThreadUtil.postOnUiThread(() -> addRemoteMessage(text)));
   }
 
   @Override
@@ -133,7 +127,6 @@
     rttMessages.get(lastIndexOfLocalMessage).finish();
     notifyItemChanged(lastIndexOfLocalMessage);
     lastIndexOfLocalMessage = -1;
-    startChatBot();
   }
 
   void addRemoteMessage(String message) {
@@ -146,73 +139,4 @@
       messageListener.newMessageAdded();
     }
   }
-
-  private void startChatBot() {
-    typeBot.scheduleMessage();
-  }
-
-  // TODO(wangqi): Move this out of this class once a bug is fixed.
-  private static class TypeBot {
-    interface Callback {
-      void type(String text);
-    }
-
-    private static final String[] CANDIDATE_MESSAGES =
-        new String[] {
-          "To RTT or not to RTT, that is the question...",
-          "Making TTY great again!",
-          "I would be more comfortable with real \"Thyme\" chatting."
-              + " I don't know how to end this pun",
-          "お疲れ様でした",
-          "The FCC has mandated that I respond... I will do so begrudgingly",
-          "😂😂😂💯"
-        };
-    private final Random random = new Random();
-    private final Callback callback;
-    private final List<String> messageQueue = new ArrayList<>();
-    private int currentTypingPosition = -1;
-    private String currentTypingMessage = null;
-
-    TypeBot(Callback callback) {
-      this.callback = callback;
-    }
-
-    @MainThread
-    public void scheduleMessage() {
-      Assert.isMainThread();
-      if (random.nextDouble() < 0.5) {
-        return;
-      }
-
-      String text = CANDIDATE_MESSAGES[random.nextInt(CANDIDATE_MESSAGES.length)];
-      messageQueue.add(text);
-      typeMessage();
-    }
-
-    @MainThread
-    private void typeMessage() {
-      Assert.isMainThread();
-      if (currentTypingPosition < 0 || currentTypingMessage == null) {
-        if (messageQueue.size() <= 0) {
-          return;
-        }
-        currentTypingMessage = messageQueue.remove(0);
-        currentTypingPosition = 0;
-      }
-      if (currentTypingPosition < currentTypingMessage.length()) {
-        int size = random.nextInt(currentTypingMessage.length() - currentTypingPosition + 1);
-        callback.type(
-            currentTypingMessage.substring(currentTypingPosition, currentTypingPosition + size));
-        currentTypingPosition = currentTypingPosition + size;
-        // Wait up to 2s between typing.
-        ThreadUtil.postDelayedOnUiThread(this::typeMessage, 200 * random.nextInt(10));
-      } else {
-        callback.type(RttChatMessage.BUBBLE_BREAKER);
-        currentTypingPosition = -1;
-        currentTypingMessage = null;
-        // Wait 1-11s between two messages.
-        ThreadUtil.postDelayedOnUiThread(this::typeMessage, 1000 * (1 + random.nextInt(10)));
-      }
-    }
-  }
 }
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index 0b0ad2a..c7ee2ff 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -16,21 +16,25 @@
 
 package com.android.incallui.rtt.impl;
 
+import android.app.Activity;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.telecom.CallAudioState;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.Window;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Chronometer;
@@ -38,15 +42,34 @@
 import android.widget.ImageButton;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.FragmentUtils;
+import com.android.dialer.common.LogUtil;
+import com.android.incallui.call.DialerCall.State;
+import com.android.incallui.incall.protocol.InCallButtonUi;
+import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
+import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
+import com.android.incallui.incall.protocol.InCallScreen;
+import com.android.incallui.incall.protocol.InCallScreenDelegate;
+import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
+import com.android.incallui.incall.protocol.PrimaryCallState;
+import com.android.incallui.incall.protocol.PrimaryInfo;
+import com.android.incallui.incall.protocol.SecondaryInfo;
 import com.android.incallui.rtt.impl.RttChatAdapter.MessageListener;
+import com.android.incallui.rtt.protocol.RttCallScreen;
+import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
+import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
 
 /** RTT chat fragment to show chat bubbles. */
 public class RttChatFragment extends Fragment
-    implements OnClickListener, OnEditorActionListener, TextWatcher, MessageListener {
+    implements OnEditorActionListener,
+        TextWatcher,
+        MessageListener,
+        RttCallScreen,
+        InCallScreen,
+        InCallButtonUi {
 
   private static final String ARG_CALL_ID = "call_id";
-  private static final String ARG_NAME_OR_NUMBER = "name_or_number";
-  private static final String ARG_SESSION_START_TIME = "session_start_time";
 
   private RecyclerView recyclerView;
   private RttChatAdapter adapter;
@@ -63,27 +86,59 @@
           }
         }
       };
+  private InCallScreenDelegate inCallScreenDelegate;
+  private RttCallScreenDelegate rttCallScreenDelegate;
+  private InCallButtonUiDelegate inCallButtonUiDelegate;
+  private View endCallButton;
+  private TextView nameTextView;
+  private Chronometer chronometer;
+  private boolean isTimerStarted;
 
   /**
    * Create a new instance of RttChatFragment.
    *
    * @param callId call id of the RTT call.
-   * @param nameOrNumber name or number of the caller to be displayed
-   * @param sessionStartTimeMillis start time of RTT session in terms of {@link
-   *     SystemClock#elapsedRealtime}.
    * @return new RttChatFragment
    */
-  public static RttChatFragment newInstance(
-      String callId, String nameOrNumber, long sessionStartTimeMillis) {
+  public static RttChatFragment newInstance(String callId) {
     Bundle bundle = new Bundle();
     bundle.putString(ARG_CALL_ID, callId);
-    bundle.putString(ARG_NAME_OR_NUMBER, nameOrNumber);
-    bundle.putLong(ARG_SESSION_START_TIME, sessionStartTimeMillis);
     RttChatFragment instance = new RttChatFragment();
     instance.setArguments(bundle);
     return instance;
   }
 
+  @Override
+  public void onCreate(@Nullable Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    LogUtil.i("RttChatFragment.onCreate", null);
+    inCallButtonUiDelegate =
+        FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class)
+            .newInCallButtonUiDelegate();
+    if (savedInstanceState != null) {
+      inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState);
+    }
+  }
+
+  @Override
+  public void onViewCreated(@NonNull View view, @Nullable Bundle bundle) {
+    super.onViewCreated(view, bundle);
+    LogUtil.i("RttChatFragment.onViewCreated", null);
+
+    inCallScreenDelegate =
+        FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class)
+            .newInCallScreenDelegate();
+    rttCallScreenDelegate =
+        FragmentUtils.getParentUnsafe(this, RttCallScreenDelegateFactory.class)
+            .newRttCallScreenDelegate(this);
+
+    rttCallScreenDelegate.initRttCallScreenDelegate(getContext(), this);
+
+    inCallScreenDelegate.onInCallScreenDelegateInit(this);
+    inCallScreenDelegate.onInCallScreenReady();
+    inCallButtonUiDelegate.onInCallButtonUiReady(this);
+  }
+
   @Nullable
   @Override
   public View onCreateView(
@@ -101,38 +156,27 @@
     recyclerView.setAdapter(adapter);
     recyclerView.addOnScrollListener(onScrollListener);
     submitButton = view.findViewById(R.id.rtt_chat_submit_button);
-    submitButton.setOnClickListener(this);
+    submitButton.setOnClickListener(
+        v -> {
+          adapter.submitLocalMessage();
+          isClearingInput = true;
+          editText.setText("");
+          isClearingInput = false;
+        });
     submitButton.setEnabled(false);
+    endCallButton = view.findViewById(R.id.rtt_end_call_button);
+    endCallButton.setOnClickListener(
+        v -> {
+          LogUtil.i("RttChatFragment.onClick", "end call button clicked");
+          inCallButtonUiDelegate.onEndCallClicked();
+        });
 
-    String nameOrNumber = null;
-    Bundle bundle = getArguments();
-    if (bundle != null) {
-      nameOrNumber = bundle.getString(ARG_NAME_OR_NUMBER, getString(R.string.unknown));
-    }
-    TextView nameTextView = view.findViewById(R.id.rtt_name_or_number);
-    nameTextView.setText(nameOrNumber);
-
-    long sessionStartTime = SystemClock.elapsedRealtime();
-    if (bundle != null) {
-      sessionStartTime = bundle.getLong(ARG_SESSION_START_TIME, sessionStartTime);
-    }
-    Chronometer chronometer = view.findViewById(R.id.rtt_timer);
-    chronometer.setBase(sessionStartTime);
-    chronometer.start();
+    nameTextView = view.findViewById(R.id.rtt_name_or_number);
+    chronometer = view.findViewById(R.id.rtt_timer);
     return view;
   }
 
   @Override
-  public void onClick(View v) {
-    if (v.getId() == R.id.rtt_chat_submit_button) {
-      adapter.submitLocalMessage();
-      isClearingInput = true;
-      editText.setText("");
-      isClearingInput = false;
-    }
-  }
-
-  @Override
   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
     if (actionId == EditorInfo.IME_ACTION_DONE) {
       submitButton.performClick();
@@ -166,6 +210,20 @@
     recyclerView.smoothScrollToPosition(adapter.getItemCount());
   }
 
+  @Override
+  public void onStart() {
+    LogUtil.enterBlock("RttChatFragment.onStart");
+    super.onStart();
+    onRttScreenStart();
+  }
+
+  @Override
+  public void onStop() {
+    LogUtil.enterBlock("RttChatFragment.onStop");
+    super.onStop();
+    onRttScreenStop();
+  }
+
   private void hideKeyboard() {
     InputMethodManager inputMethodManager = getContext().getSystemService(InputMethodManager.class);
     if (inputMethodManager.isAcceptingText()) {
@@ -173,4 +231,129 @@
           getActivity().getCurrentFocus().getWindowToken(), 0);
     }
   }
+
+  @Override
+  public void onRttScreenStart() {
+    rttCallScreenDelegate.onRttCallScreenUiReady();
+    Activity activity = getActivity();
+    Window window = getActivity().getWindow();
+    window.setStatusBarColor(activity.getColor(R.color.rtt_status_bar_color));
+    window.setNavigationBarColor(activity.getColor(R.color.rtt_navigation_bar_color));
+    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+  }
+
+  @Override
+  public void onRttScreenStop() {
+    rttCallScreenDelegate.onRttCallScreenUiUnready();
+  }
+
+  @Override
+  public Fragment getRttCallScreenFragment() {
+    return this;
+  }
+
+  @Override
+  public String getCallId() {
+    return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
+  }
+
+  @Override
+  public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
+    LogUtil.i("RttChatFragment.setPrimary", primaryInfo.toString());
+    nameTextView.setText(primaryInfo.name);
+  }
+
+  @Override
+  public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {}
+
+  @Override
+  public void setCallState(@NonNull PrimaryCallState primaryCallState) {
+    LogUtil.i("RttChatFragment.setCallState", primaryCallState.toString());
+    if (!isTimerStarted && primaryCallState.state == State.ACTIVE) {
+      LogUtil.i(
+          "RttChatFragment.setCallState", "starting timer with base: %d", chronometer.getBase());
+      chronometer.setBase(
+          primaryCallState.connectTimeMillis
+              - System.currentTimeMillis()
+              + SystemClock.elapsedRealtime());
+      chronometer.start();
+      isTimerStarted = true;
+    }
+  }
+
+  @Override
+  public void setEndCallButtonEnabled(boolean enabled, boolean animate) {}
+
+  @Override
+  public void showManageConferenceCallButton(boolean visible) {}
+
+  @Override
+  public boolean isManageConferenceVisible() {
+    return false;
+  }
+
+  @Override
+  public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {}
+
+  @Override
+  public void showNoteSentToast() {}
+
+  @Override
+  public void updateInCallScreenColors() {}
+
+  @Override
+  public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {}
+
+  @Override
+  public int getAnswerAndDialpadContainerResourceId() {
+    return 0;
+  }
+
+  @Override
+  public void showLocationUi(Fragment locationUi) {}
+
+  @Override
+  public boolean isShowingLocationUi() {
+    return false;
+  }
+
+  @Override
+  public Fragment getInCallScreenFragment() {
+    return this;
+  }
+
+  @Override
+  public void showButton(int buttonId, boolean show) {}
+
+  @Override
+  public void enableButton(int buttonId, boolean enable) {}
+
+  @Override
+  public void setEnabled(boolean on) {}
+
+  @Override
+  public void setHold(boolean on) {}
+
+  @Override
+  public void setCameraSwitched(boolean isBackFacingCamera) {}
+
+  @Override
+  public void setVideoPaused(boolean isPaused) {}
+
+  @Override
+  public void setAudioState(CallAudioState audioState) {}
+
+  @Override
+  public void updateButtonStates() {}
+
+  @Override
+  public void updateInCallButtonUiColors(int color) {}
+
+  @Override
+  public Fragment getInCallButtonUiFragment() {
+    return this;
+  }
+
+  @Override
+  public void showAudioRouteSelector() {}
 }
diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/impl/RttChatMessage.java
index 85b0451..b36da77 100644
--- a/java/com/android/incallui/rtt/impl/RttChatMessage.java
+++ b/java/com/android/incallui/rtt/impl/RttChatMessage.java
@@ -19,6 +19,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextWatcher;
+import com.android.incallui.rtt.protocol.Constants;
 import com.google.common.base.Splitter;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -27,8 +28,7 @@
 /** Message class that holds one RTT chat content. */
 final class RttChatMessage {
 
-  static final String BUBBLE_BREAKER = "\n\n";
-  private static final Splitter SPLITTER = Splitter.on(BUBBLE_BREAKER);
+  private static final Splitter SPLITTER = Splitter.on(Constants.BUBBLE_BREAKER);
 
   boolean isRemote;
   public boolean hasAvatar;
@@ -108,7 +108,7 @@
       firstMessage.isRemote = true;
     }
     firstMessage.append(firstMessageContent);
-    if (splitText.hasNext() || text.endsWith(BUBBLE_BREAKER)) {
+    if (splitText.hasNext() || text.endsWith(Constants.BUBBLE_BREAKER)) {
       firstMessage.finish();
     }
     messageList.add(firstMessage);
diff --git a/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
index 7ba6a09..5ba9f4e 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
+++ b/java/com/android/incallui/rtt/impl/res/layout/frag_rtt_chat.xml
@@ -17,7 +17,8 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/dialer_theme_color">
+    android:background="@color/dialer_theme_color"
+    android:fitsSystemWindows="true">
 
   <include layout="@layout/rtt_banner"/>
 
@@ -52,6 +53,8 @@
         android:inputType="textMultiLine|text"
         android:maxLines="4"
         android:minHeight="53dp"
+        android:textColor="#DD000000"
+        android:textColorHint="#757575"
         android:textSize="16sp"/>
     <ImageButton
         android:id="@+id/rtt_chat_submit_button"
diff --git a/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml b/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml
index 4ce94f9..f193805 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml
+++ b/java/com/android/incallui/rtt/impl/res/layout/rtt_banner.xml
@@ -17,79 +17,61 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="56dp"
-    android:background="#FAFAFA"
+    android:layout_height="?attr/actionBarSize"
+    android:background="#F305228F"
     android:elevation="3dp">
   <ImageButton
-      android:id="@+id/rtt_back"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
+      android:id="@+id/rtt_end_call_button"
+      android:layout_width="32dp"
+      android:layout_height="32dp"
       android:layout_marginStart="16dp"
       android:layout_alignParentStart="true"
       android:layout_centerVertical="true"
       android:background="@android:color/transparent"
-      android:contentDescription="@string/content_description_rtt_back_button"
-      android:src="@drawable/quantum_ic_arrow_back_vd_theme_24"
-      android:tint="#DF000000"/>
+      android:contentDescription="@string/incall_content_description_end_call"
+      android:scaleType="fitXY"
+      android:src="@drawable/quantum_ic_call_end_vd_theme_24"
+      android:tint="#FFDF0000"/>
   <LinearLayout
-      android:layout_width="wrap_content"
+      android:layout_width="260dp"
       android:layout_height="match_parent"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       android:layout_marginStart="32dp"
-      android:layout_toEndOf="@id/rtt_back"
+      android:layout_toEndOf="@id/rtt_end_call_button"
       android:orientation="vertical">
     <TextView
         android:id="@+id/rtt_name_or_number"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
         android:fontFamily="sans-serif-medium"
         android:includeFontPadding="false"
-        android:textColor="#DD000000"
+        android:textColor="#FFFFFF"
         android:textSize="20sp"
         tools:text="Bruce Graham"/>
     <Chronometer
         android:id="@+id/rtt_timer"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
         android:fontFamily="sans-serif-medium"
         android:includeFontPadding="false"
-        android:textColor="#DD000000"
+        android:textColor="#FFFFFF"
         android:textSize="14sp"
         tools:text="00:09"/>
   </LinearLayout>
   <ImageButton
-      android:id="@+id/rtt_hang_up_button"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginEnd="16dp"
+      android:id="@+id/rtt_overflow_button"
+      android:layout_width="32dp"
+      android:layout_height="32dp"
+      android:layout_marginEnd="12dp"
       android:layout_alignParentEnd="true"
       android:layout_centerVertical="true"
       android:background="@android:color/transparent"
-      android:contentDescription="@string/incall_content_description_end_call"
-      android:src="@drawable/quantum_ic_call_end_vd_theme_24"
-      android:tint="#FFDF0000"/>
-  <ImageButton
-      android:id="@+id/rtt_speaker_button"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginEnd="24dp"
-      android:layout_centerVertical="true"
-      android:layout_toStartOf="@id/rtt_hang_up_button"
-      android:background="@android:color/transparent"
-      android:contentDescription="@string/incall_content_description_speaker"
-      android:src="@drawable/quantum_ic_volume_up_vd_theme_24"
-      android:tint="#DD000000"/>
-  <ImageButton
-      android:id="@+id/rtt_mic_button"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginEnd="24dp"
-      android:layout_centerVertical="true"
-      android:layout_toStartOf="@id/rtt_speaker_button"
-      android:background="@android:color/transparent"
-      android:contentDescription="@string/incall_content_description_unmuted"
-      android:src="@drawable/quantum_ic_mic_off_vd_theme_24"
-      android:tint="#DD000000"/>
+      android:contentDescription="@string/content_description_overflow"
+      android:scaleType="fitXY"
+      android:src="@drawable/quantum_ic_more_vert_vd_theme_24"
+      android:tint="#FFFFFF"/>
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/colors.xml b/java/com/android/incallui/rtt/impl/res/values/colors.xml
index 402cac4..c25ad21 100644
--- a/java/com/android/incallui/rtt/impl/res/values/colors.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/colors.xml
@@ -15,5 +15,6 @@
   ~ limitations under the License
   -->
 <resources>
-  <color name="rtt_status_bar_color">#E0E0E0</color>
+  <color name="rtt_status_bar_color">#03165C</color>
+  <color name="rtt_navigation_bar_color">#FAFAFA</color>
 </resources>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/strings.xml b/java/com/android/incallui/rtt/impl/res/values/strings.xml
index 523abdc..0b9eb71 100644
--- a/java/com/android/incallui/rtt/impl/res/values/strings.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/strings.xml
@@ -18,9 +18,6 @@
   <!-- Content description for submit chat input button. [CHAR LIMIT=NONE] -->
   <string name="content_description_rtt_check_button">Go ahead</string>
 
-  <!-- Content description for navigate back button on RTT chat window. [CHAR LIMIT=NONE] -->
-  <string name="content_description_rtt_back_button">Back</string>
-
   <!-- Hint text for input box. [CHAR LIMIT=NONE] -->
   <string name="rtt_input_hint">Type a message</string>
 
diff --git a/java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml b/java/com/android/incallui/rtt/protocol/AndroidManifest.xml
similarity index 62%
rename from java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml
rename to java/com/android/incallui/rtt/protocol/AndroidManifest.xml
index b48e8d4..52514a5 100644
--- a/java/com/android/incallui/rtt/impl/res/layout/activity_rtt.xml
+++ b/java/com/android/incallui/rtt/protocol/AndroidManifest.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2018 The Android Open Source Project
   ~
@@ -14,13 +13,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-  <FrameLayout
-      android:id="@+id/fragment_rtt"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"/>
 
-</LinearLayout>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.incallui.rtt.protocol">
+  <uses-sdk
+      android:minSdkVersion="23"
+      android:targetSdkVersion="26"/>
+</manifest>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/protocol/Constants.java b/java/com/android/incallui/rtt/protocol/Constants.java
new file mode 100644
index 0000000..5806bba
--- /dev/null
+++ b/java/com/android/incallui/rtt/protocol/Constants.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 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.incallui.rtt.protocol;
+
+/** Constants for RTT call. */
+public interface Constants {
+
+  /** String used to break bubble, which means one RTT message is complete. */
+  String BUBBLE_BREAKER = "\n\n";
+}
diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreen.java b/java/com/android/incallui/rtt/protocol/RttCallScreen.java
new file mode 100644
index 0000000..afacbae
--- /dev/null
+++ b/java/com/android/incallui/rtt/protocol/RttCallScreen.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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.incallui.rtt.protocol;
+
+import android.support.v4.app.Fragment;
+
+/** Interface for call RTT call module. */
+public interface RttCallScreen {
+
+  void onRttScreenStart();
+
+  void onRttScreenStop();
+
+  Fragment getRttCallScreenFragment();
+
+  String getCallId();
+}
diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java
new file mode 100644
index 0000000..e29c43d
--- /dev/null
+++ b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.incallui.rtt.protocol;
+
+import android.content.Context;
+
+/** Callbacks from the module out to the container. */
+public interface RttCallScreenDelegate {
+
+  void initRttCallScreenDelegate(Context context, RttCallScreen rttCallScreen);
+
+  void onRttCallScreenUiReady();
+
+  void onRttCallScreenUiUnready();
+}
diff --git a/java/com/android/incallui/rtt/protocol/RttCallScreenDelegateFactory.java b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegateFactory.java
new file mode 100644
index 0000000..0dbcc91
--- /dev/null
+++ b/java/com/android/incallui/rtt/protocol/RttCallScreenDelegateFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 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.incallui.rtt.protocol;
+
+/** Callbacks from the module out to the container. */
+public interface RttCallScreenDelegateFactory {
+
+  RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen rttCallScreen);
+}
diff --git a/packages.mk b/packages.mk
index bc98ef5..304db49 100644
--- a/packages.mk
+++ b/packages.mk
@@ -81,6 +81,7 @@
 	com.android.incallui.incall.impl \
 	com.android.incallui.maps.impl \
 	com.android.incallui.rtt.impl \
+	com.android.incallui.rtt.protocol \
   com.android.incallui.speakeasy \
 	com.android.incallui.sessiondata \
 	com.android.incallui.spam \