Merge changes I6ef6593a,Iaa73b935
am: 9bd89a3096

Change-Id: I7382099d1e89de35a02cd663b1232800dd66d161
diff --git a/assets/quantum/res/drawable/quantum_ic_add_call_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_add_call_vd_theme_24.xml
new file mode 100644
index 0000000..b7d3a09
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_add_call_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.59l2.2,-2.21c0.28,-0.26 0.36,-0.65 0.25,-1C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM21,6h-3V3h-2v3h-3v2h3v3h2V8h3z"/>
+</vector>
\ No newline at end of file
diff --git a/assets/quantum/res/drawable/quantum_ic_dialpad_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_dialpad_vd_theme_24.xml
new file mode 100644
index 0000000..4e340ed
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_dialpad_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,19c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM6,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,7c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,1c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
\ No newline at end of file
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index 635d8fd..1b0d626 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -659,21 +659,21 @@
     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;
+    MAIN_SWITCH_TAB_TO_FAVORITE = 1327;
+    MAIN_SWITCH_TAB_TO_CALL_LOG = 1328;
+    MAIN_SWITCH_TAB_TO_CONTACTS = 1329;
+    MAIN_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;
+    MAIN_TOUCH_DIALPAD_SEARCH_LIST_TO_CLOSE_SEARCH_AND_DIALPAD = 1331;
+    MAIN_TOUCH_DIALPAD_SEARCH_LIST_TO_HIDE_DIALPAD = 1332;
+    MAIN_TOUCH_SEARCH_LIST_TO_CLOSE_SEARCH = 1333;
+    MAIN_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD = 1334;
+    MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH = 1335;
+    MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH_AND_DIALPAD = 1336;
+    MAIN_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD = 1337;
+    MAIN_CLICK_SEARCH_BAR = 1338;
+    MAIN_CLICK_SEARCH_BAR_VOICE_BUTTON = 1339;
     // NUI FAB
-    NUI_CLICK_FAB_TO_OPEN_DIALPAD = 1340;
+    MAIN_CLICK_FAB_TO_OPEN_DIALPAD = 1340;
   }
 }
diff --git a/java/com/android/dialer/main/impl/MainSearchController.java b/java/com/android/dialer/main/impl/MainSearchController.java
index 7d380d7..a6bdec5 100644
--- a/java/com/android/dialer/main/impl/MainSearchController.java
+++ b/java/com/android/dialer/main/impl/MainSearchController.java
@@ -228,21 +228,21 @@
       if (TextUtils.isEmpty(getDialpadFragment().getQuery())) {
         Logger.get(mainActivity)
             .logImpression(
-                DialerImpression.Type.NUI_TOUCH_DIALPAD_SEARCH_LIST_TO_CLOSE_SEARCH_AND_DIALPAD);
+                DialerImpression.Type.MAIN_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);
+            .logImpression(DialerImpression.Type.MAIN_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);
+            .logImpression(DialerImpression.Type.MAIN_TOUCH_SEARCH_LIST_TO_CLOSE_SEARCH);
         closeSearch(true);
       } else {
         Logger.get(mainActivity)
-            .logImpression(DialerImpression.Type.NUI_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD);
+            .logImpression(DialerImpression.Type.MAIN_TOUCH_SEARCH_LIST_TO_HIDE_KEYBOARD);
         toolbar.hideKeyboard();
       }
     }
@@ -257,7 +257,7 @@
     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);
+          .logImpression(DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_HIDE_DIALPAD);
       hideDialpad(/* animate=*/ true, /* bottomNavVisible=*/ false);
       return true;
     } else if (isSearchVisible()) {
@@ -265,8 +265,8 @@
       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);
+                  ? DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH_AND_DIALPAD
+                  : DialerImpression.Type.MAIN_PRESS_BACK_BUTTON_TO_CLOSE_SEARCH);
       closeSearch(true);
       return true;
     } else {
@@ -346,7 +346,7 @@
   @Override
   public void onSearchBarClicked() {
     LogUtil.enterBlock("MainSearchController.onSearchBarClicked");
-    Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR);
+    Logger.get(mainActivity).logImpression(DialerImpression.Type.MAIN_CLICK_SEARCH_BAR);
     openSearch(Optional.absent());
   }
 
@@ -406,7 +406,8 @@
 
   @Override
   public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) {
-    Logger.get(mainActivity).logImpression(DialerImpression.Type.NUI_CLICK_SEARCH_BAR_VOICE_BUTTON);
+    Logger.get(mainActivity)
+        .logImpression(DialerImpression.Type.MAIN_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/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
index 8af41a8..e87fb0e 100644
--- a/java/com/android/dialer/main/impl/OldMainActivityPeer.java
+++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
@@ -203,7 +203,7 @@
     fab.setOnClickListener(
         v -> {
           Logger.get(mainActivity)
-              .logImpression(DialerImpression.Type.NUI_CLICK_FAB_TO_OPEN_DIALPAD);
+              .logImpression(DialerImpression.Type.MAIN_CLICK_FAB_TO_OPEN_DIALPAD);
           searchController.showDialpad(true);
         });
 
@@ -1106,7 +1106,7 @@
     public void onSpeedDialSelected() {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onSpeedDialSelected");
       if (selectedTab != TabIndex.SPEED_DIAL) {
-        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_FAVORITE);
+        Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_FAVORITE);
         selectedTab = TabIndex.SPEED_DIAL;
       }
       hideAllFragments();
@@ -1126,7 +1126,7 @@
     public void onCallLogSelected() {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onCallLogSelected");
       if (selectedTab != TabIndex.CALL_LOG) {
-        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CALL_LOG);
+        Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_CALL_LOG);
         selectedTab = TabIndex.CALL_LOG;
       }
       hideAllFragments();
@@ -1146,7 +1146,7 @@
     public void onContactsSelected() {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onContactsSelected");
       if (selectedTab != TabIndex.CONTACTS) {
-        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_CONTACTS);
+        Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_CONTACTS);
         selectedTab = TabIndex.CONTACTS;
       }
       hideAllFragments();
@@ -1170,7 +1170,7 @@
     public void onVoicemailSelected() {
       LogUtil.enterBlock("MainBottomNavBarBottomNavTabListener.onVoicemailSelected");
       if (selectedTab != TabIndex.VOICEMAIL) {
-        Logger.get(context).logImpression(DialerImpression.Type.NUI_SWITCH_TAB_TO_VOICEMAIL);
+        Logger.get(context).logImpression(DialerImpression.Type.MAIN_SWITCH_TAB_TO_VOICEMAIL);
         selectedTab = TabIndex.VOICEMAIL;
       }
       hideAllFragments();
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index deb205c..396b89e 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -29,6 +29,7 @@
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -45,6 +46,8 @@
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.FragmentUtils;
 import com.android.dialer.common.LogUtil;
+import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment;
+import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
 import com.android.incallui.call.DialerCall.State;
 import com.android.incallui.incall.protocol.InCallButtonUi;
 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
@@ -68,7 +71,8 @@
         MessageListener,
         RttCallScreen,
         InCallScreen,
-        InCallButtonUi {
+        InCallButtonUi,
+        AudioRouteSelectorPresenter {
 
   private static final String ARG_CALL_ID = "call_id";
 
@@ -94,6 +98,7 @@
   private TextView nameTextView;
   private Chronometer chronometer;
   private boolean isTimerStarted;
+  private RttOverflowMenu overflowMenu;
 
   /**
    * Create a new instance of RttChatFragment.
@@ -173,6 +178,10 @@
           inCallButtonUiDelegate.onEndCallClicked();
         });
 
+    overflowMenu = new RttOverflowMenu(getContext(), inCallButtonUiDelegate);
+    view.findViewById(R.id.rtt_overflow_button)
+        .setOnClickListener(v -> overflowMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, 0));
+
     nameTextView = view.findViewById(R.id.rtt_name_or_number);
     chronometer = view.findViewById(R.id.rtt_timer);
     return view;
@@ -240,6 +249,9 @@
   public void onStop() {
     LogUtil.enterBlock("RttChatFragment.onStop");
     super.onStop();
+    if (overflowMenu.isShowing()) {
+      overflowMenu.dismiss();
+    }
     onRttScreenStop();
   }
 
@@ -360,7 +372,11 @@
   public void setVideoPaused(boolean isPaused) {}
 
   @Override
-  public void setAudioState(CallAudioState audioState) {}
+  public void setAudioState(CallAudioState audioState) {
+    LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState);
+    overflowMenu.setMuteButtonChecked(audioState.isMuted());
+    overflowMenu.setAudioState(audioState);
+  }
 
   @Override
   public void updateButtonStates() {}
@@ -374,5 +390,16 @@
   }
 
   @Override
-  public void showAudioRouteSelector() {}
+  public void showAudioRouteSelector() {
+    AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
+        .show(getChildFragmentManager(), null);
+  }
+
+  @Override
+  public void onAudioRouteSelected(int audioRoute) {
+    inCallButtonUiDelegate.setAudioRoute(audioRoute);
+  }
+
+  @Override
+  public void onAudioRouteSelectorDismiss() {}
 }
diff --git a/java/com/android/incallui/rtt/impl/RttCheckableButton.java b/java/com/android/incallui/rtt/impl/RttCheckableButton.java
new file mode 100644
index 0000000..ba15ca9
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/RttCheckableButton.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2016 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.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.SoundEffectConstants;
+import android.widget.Checkable;
+import android.widget.TextView;
+
+/** Image button that maintains a checked state. */
+public class RttCheckableButton extends TextView implements Checkable {
+
+  private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
+
+  /** Callback interface to notify when the button's checked state has changed */
+  public interface OnCheckedChangeListener {
+
+    void onCheckedChanged(RttCheckableButton button, boolean isChecked);
+  }
+
+  private boolean broadcasting;
+  private boolean isChecked;
+  private OnCheckedChangeListener onCheckedChangeListener;
+  private CharSequence contentDescriptionChecked;
+  private CharSequence contentDescriptionUnchecked;
+
+  public RttCheckableButton(Context context) {
+    this(context, null);
+  }
+
+  public RttCheckableButton(Context context, AttributeSet attrs) {
+    this(context, attrs, 0);
+  }
+
+  public RttCheckableButton(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+    init(context, attrs);
+  }
+
+  private void init(Context context, AttributeSet attrs) {
+    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RttCheckableButton);
+    setChecked(typedArray.getBoolean(R.styleable.RttCheckableButton_android_checked, false));
+    contentDescriptionChecked =
+        typedArray.getText(R.styleable.RttCheckableButton_contentDescriptionChecked);
+    contentDescriptionUnchecked =
+        typedArray.getText(R.styleable.RttCheckableButton_contentDescriptionUnchecked);
+    typedArray.recycle();
+
+    updateContentDescription();
+    setClickable(true);
+    setFocusable(true);
+  }
+
+  @Override
+  public void setChecked(boolean checked) {
+    performSetChecked(checked);
+  }
+
+  /**
+   * Called when the state of the button should be updated, this should not be the result of user
+   * interaction.
+   *
+   * @param checked {@code true} if the button should be in the checked state, {@code false}
+   *     otherwise.
+   */
+  private void performSetChecked(boolean checked) {
+    if (isChecked() == checked) {
+      return;
+    }
+    isChecked = checked;
+    CharSequence contentDescription = updateContentDescription();
+    announceForAccessibility(contentDescription);
+    refreshDrawableState();
+  }
+
+  private CharSequence updateContentDescription() {
+    CharSequence contentDescription =
+        isChecked ? contentDescriptionChecked : contentDescriptionUnchecked;
+    setContentDescription(contentDescription);
+    return contentDescription;
+  }
+
+  /**
+   * Called when the user interacts with a button. This should not result in the button updating
+   * state, rather the request should be propagated to the associated listener.
+   *
+   * @param checked {@code true} if the button should be in the checked state, {@code false}
+   *     otherwise.
+   */
+  private void userRequestedSetChecked(boolean checked) {
+    if (isChecked() == checked) {
+      return;
+    }
+    if (broadcasting) {
+      return;
+    }
+    broadcasting = true;
+    if (onCheckedChangeListener != null) {
+      onCheckedChangeListener.onCheckedChanged(this, checked);
+    }
+    broadcasting = false;
+  }
+
+  @Override
+  public boolean isChecked() {
+    return isChecked;
+  }
+
+  @Override
+  public void toggle() {
+    userRequestedSetChecked(!isChecked());
+  }
+
+  @Override
+  public int[] onCreateDrawableState(int extraSpace) {
+    final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+    if (isChecked()) {
+      mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+    }
+    return drawableState;
+  }
+
+  @Override
+  protected void drawableStateChanged() {
+    super.drawableStateChanged();
+    invalidate();
+  }
+
+  public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
+    this.onCheckedChangeListener = listener;
+  }
+
+  @Override
+  public boolean performClick() {
+    if (!isCheckable()) {
+      return super.performClick();
+    }
+
+    toggle();
+    final boolean handled = super.performClick();
+    if (!handled) {
+      // View only makes a sound effect if the onClickListener was
+      // called, so we'll need to make one here instead.
+      playSoundEffect(SoundEffectConstants.CLICK);
+    }
+    return handled;
+  }
+
+  private boolean isCheckable() {
+    return onCheckedChangeListener != null;
+  }
+
+  @Override
+  public void onRestoreInstanceState(Parcelable state) {
+    SavedState savedState = (SavedState) state;
+    super.onRestoreInstanceState(savedState.getSuperState());
+    performSetChecked(savedState.isChecked);
+    requestLayout();
+  }
+
+  @Override
+  public Parcelable onSaveInstanceState() {
+    return new SavedState(isChecked(), super.onSaveInstanceState());
+  }
+
+  private static class SavedState extends BaseSavedState {
+
+    public final boolean isChecked;
+
+    private SavedState(boolean isChecked, Parcelable superState) {
+      super(superState);
+      this.isChecked = isChecked;
+    }
+
+    protected SavedState(Parcel in) {
+      super(in);
+      isChecked = in.readByte() != 0;
+    }
+
+    public static final Creator<SavedState> CREATOR =
+        new Creator<SavedState>() {
+          @Override
+          public SavedState createFromParcel(Parcel in) {
+            return new SavedState(in);
+          }
+
+          @Override
+          public SavedState[] newArray(int size) {
+            return new SavedState[size];
+          }
+        };
+
+    @Override
+    public int describeContents() {
+      return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+      super.writeToParcel(dest, flags);
+      dest.writeByte((byte) (isChecked ? 1 : 0));
+    }
+  }
+}
diff --git a/java/com/android/incallui/rtt/impl/RttOverflowMenu.java b/java/com/android/incallui/rtt/impl/RttOverflowMenu.java
new file mode 100644
index 0000000..e0916be
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/RttOverflowMenu.java
@@ -0,0 +1,78 @@
+/*
+ * 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.content.Context;
+import android.telecom.CallAudioState;
+import android.view.View;
+import android.widget.PopupWindow;
+import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
+import com.android.incallui.rtt.impl.RttCheckableButton.OnCheckedChangeListener;
+import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo;
+import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo.IconSize;
+
+/** Overflow menu for RTT call. */
+public class RttOverflowMenu extends PopupWindow implements OnCheckedChangeListener {
+
+  private final RttCheckableButton muteButton;
+  private final RttCheckableButton speakerButton;
+  private final RttCheckableButton dialpadButton;
+  private final RttCheckableButton addCallButton;
+  private final InCallButtonUiDelegate inCallButtonUiDelegate;
+
+  RttOverflowMenu(Context context, InCallButtonUiDelegate inCallButtonUiDelegate) {
+    super(context);
+    this.inCallButtonUiDelegate = inCallButtonUiDelegate;
+    View view = View.inflate(context, R.layout.overflow_menu, null);
+    setContentView(view);
+    setOnDismissListener(this::dismiss);
+    setFocusable(true);
+    setWidth(context.getResources().getDimensionPixelSize(R.dimen.rtt_overflow_menu_width));
+    muteButton = view.findViewById(R.id.menu_mute);
+    muteButton.setOnCheckedChangeListener(this);
+    speakerButton = view.findViewById(R.id.menu_speaker);
+    speakerButton.setOnCheckedChangeListener(this);
+    dialpadButton = view.findViewById(R.id.menu_keypad);
+    dialpadButton.setOnCheckedChangeListener(this);
+    addCallButton = view.findViewById(R.id.menu_add_call);
+    addCallButton.setOnCheckedChangeListener(this);
+  }
+
+  @Override
+  public void onCheckedChanged(RttCheckableButton button, boolean isChecked) {
+    if (button == muteButton) {
+      inCallButtonUiDelegate.muteClicked(isChecked, true);
+    } else if (button == speakerButton) {
+      inCallButtonUiDelegate.toggleSpeakerphone();
+    } else if (button == dialpadButton) {
+      inCallButtonUiDelegate.showDialpadClicked(isChecked);
+    } else if (button == addCallButton) {
+      inCallButtonUiDelegate.addCallClicked();
+    }
+  }
+
+  void setMuteButtonChecked(boolean isChecked) {
+    muteButton.setChecked(isChecked);
+  }
+
+  void setAudioState(CallAudioState audioState) {
+    SpeakerButtonInfo info = new SpeakerButtonInfo(audioState, IconSize.SIZE_24_DP);
+    if (info.checkable) {
+      speakerButton.setChecked(info.isChecked);
+    }
+  }
+}
diff --git a/java/com/android/incallui/rtt/impl/res/color/rtt_checkable_button_color.xml b/java/com/android/incallui/rtt/impl/res/color/rtt_checkable_button_color.xml
new file mode 100644
index 0000000..cb3a452
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/res/color/rtt_checkable_button_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_checked="true"
+      android:color="@color/rtt_button_selected_color" />
+  <item android:color="@color/rtt_button_unselected_color" />
+</selector>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml b/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml
new file mode 100644
index 0000000..6142986
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/res/drawable/overflow_menu_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@android:color/white"/>
+  <corners android:radius="2dp"/>
+</shape>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml b/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml
new file mode 100644
index 0000000..a29fad5
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/res/layout/overflow_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="180dp"
+    android:layout_height="wrap_content"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp"
+    android:background="@drawable/overflow_menu_background"
+    android:orientation="vertical">
+  <com.android.incallui.rtt.impl.RttCheckableButton
+      android:id="@+id/menu_mute"
+      style="@style/RttButton"
+      android:drawableStart="@drawable/quantum_ic_mic_off_vd_theme_24"
+      android:text="@string/incall_label_mute"
+      app:contentDescriptionChecked="@string/incall_content_description_muted"
+      app:contentDescriptionUnchecked="@string/incall_content_description_unmuted"/>
+  <com.android.incallui.rtt.impl.RttCheckableButton
+      android:id="@+id/menu_speaker"
+      style="@style/RttButton"
+      android:drawableStart="@drawable/quantum_ic_volume_up_vd_theme_24"
+      android:text="@string/incall_label_speaker"
+      app:contentDescriptionChecked="@string/incall_content_description_speaker"
+      app:contentDescriptionUnchecked="@string/incall_content_description_earpiece"/>
+  <com.android.incallui.rtt.impl.RttCheckableButton
+      android:id="@+id/menu_keypad"
+      style="@style/RttButton"
+      android:drawableStart="@drawable/quantum_ic_dialpad_vd_theme_24"
+      android:text="@string/incall_label_dialpad"/>
+  <com.android.incallui.rtt.impl.RttCheckableButton
+      android:id="@+id/menu_add_call"
+      style="@style/RttButton"
+      android:drawableStart="@drawable/quantum_ic_add_call_vd_theme_24"
+      android:text="@string/incall_label_add_call"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/attrs.xml b/java/com/android/incallui/rtt/impl/res/values/attrs.xml
new file mode 100644
index 0000000..2e7d899
--- /dev/null
+++ b/java/com/android/incallui/rtt/impl/res/values/attrs.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<resources>
+  <declare-styleable name="RttCheckableButton">
+    <attr name="android:checked"/>
+    <attr name="contentDescriptionChecked" format="reference|string"/>
+    <attr name="contentDescriptionUnchecked" format="reference|string"/>
+  </declare-styleable>
+</resources>
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 c25ad21..e1702cc 100644
--- a/java/com/android/incallui/rtt/impl/res/values/colors.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/colors.xml
@@ -17,4 +17,6 @@
 <resources>
   <color name="rtt_status_bar_color">#03165C</color>
   <color name="rtt_navigation_bar_color">#FAFAFA</color>
+  <color name="rtt_button_unselected_color">#757575</color>
+  <color name="rtt_button_selected_color">#2A56C6</color>
 </resources>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/dimens.xml b/java/com/android/incallui/rtt/impl/res/values/dimens.xml
index a3f230c..4c3fe02 100644
--- a/java/com/android/incallui/rtt/impl/res/values/dimens.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/dimens.xml
@@ -17,4 +17,5 @@
 <resources>
   <dimen name="rtt_message_margin_top">16dp</dimen>
   <dimen name="rtt_same_group_message_margin_top">2dp</dimen>
+  <dimen name="rtt_overflow_menu_width">180dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/java/com/android/incallui/rtt/impl/res/values/styles.xml b/java/com/android/incallui/rtt/impl/res/values/styles.xml
index 55207a3..515e0df 100644
--- a/java/com/android/incallui/rtt/impl/res/values/styles.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/styles.xml
@@ -15,9 +15,24 @@
   ~ limitations under the License
   -->
 <resources>
+
   <style name="Dialer.Incall.TextAppearance.RttMessage" parent="Dialer.Incall.TextAppearance">
     <item name="android:fontFamily">sans-serif</item>
     <item name="android:textColor">#DD000000</item>
     <item name="android:textSize">16sp</item>
   </style>
+
+  <style name="RttButton">
+    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_height">wrap_content</item>
+    <item name="android:drawablePadding">16dp</item>
+    <item name="android:paddingLeft">16dp</item>
+    <item name="android:paddingRight">16dp</item>
+    <item name="android:paddingTop">8dp</item>
+    <item name="android:paddingBottom">8dp</item>
+    <item name="android:drawableTint">@color/rtt_checkable_button_color</item>
+    <item name="android:textSize">16sp</item>
+    <item name="android:textColor">@color/rtt_checkable_button_color</item>
+    <item name="android:colorControlHighlight">#33000000</item>
+  </style>
 </resources>
\ No newline at end of file