WIP - dialpad and dtmf sending

Ketroc created

Change summary

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java           |  60 
src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java           |  73 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java |  12 
src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java         |   2 
src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java       |  37 
src/main/res/layout/activity_rtp_session.xml                              |  18 
src/main/res/layout/dialpad.xml                                           | 368 
src/main/res/menu/activity_rtp_session.xml                                |   6 
src/main/res/values/dimens.xml                                            |   7 
src/main/res/values/strings.xml                                           |   1 
src/main/res/values/styles.xml                                            |  12 
11 files changed, 589 insertions(+), 7 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java 🔗

@@ -38,6 +38,7 @@ import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 
 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.jetbrains.annotations.NotNull;
 import org.webrtc.RendererCommon;
 import org.webrtc.SurfaceViewRenderer;
 import org.webrtc.VideoTrack;
@@ -149,6 +150,35 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
         this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
         setSupportActionBar(binding.toolbar);
+
+        //TODO: remove this - for testing dialpad input
+        //((DialpadView)findViewById(R.id.action_dialpad)).
+
+        findViewById(R.id.dialpad_1_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_2_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_3_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_4_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_5_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_6_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_7_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_8_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_9_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_0_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(view -> dialpadPressed(view));
+        findViewById(R.id.dialpad_pound_holder).setOnClickListener(view -> dialpadPressed(view));
+
+        if (savedInstanceState != null) {
+            int dialpad_visibility = savedInstanceState.getInt("dialpad_visibility");
+            System.out.println("dialpad_visibility onCreate = " + dialpad_visibility);
+            findViewById(R.id.dialpad).setVisibility(dialpad_visibility);
+        }
+    }
+
+
+
+    private void dialpadPressed(View dialpadKeyHolderView) {
+        JingleRtpConnection rtpConnection = requireRtpConnection();
+        rtpConnection.applyDtmfTone(dialpadKeyHolderView.getTag().toString());
     }
 
     @Override
@@ -156,8 +186,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
         getMenuInflater().inflate(R.menu.activity_rtp_session, menu);
         final MenuItem help = menu.findItem(R.id.action_help);
         final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat);
+        final MenuItem dialpad = menu.findItem(R.id.action_dialpad);
         help.setVisible(isHelpButtonVisible());
         gotoChat.setVisible(isSwitchToConversationVisible());
+        dialpad.setVisible(isAudioOnlyConversation());
         return super.onCreateOptionsMenu(menu);
     }
 
@@ -192,12 +224,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
         return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
     }
 
+    private boolean isAudioOnlyConversation() {
+        final JingleRtpConnection connection =
+                this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+        return connection != null &&
+                connection.getEndUserState() == RtpEndUserState.CONNECTED &&
+                !connection.isVideoEnabled();
+    }
+
     private void switchToConversation() {
         final Contact contact = getWith();
         final Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
         switchToConversation(conversation);
     }
 
+    private void toggleDialpadVisibility() {
+        if (binding.dialpad.getVisibility() == View.VISIBLE) {
+            binding.dialpad.setVisibility(View.GONE);
+        }
+        else {
+            binding.dialpad.setVisibility(View.VISIBLE);
+        }
+    }
+
     public boolean onOptionsItemSelected(final MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_help:
@@ -206,6 +255,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
             case R.id.action_goto_chat:
                 switchToConversation();
                 break;
+            case R.id.action_dialpad:
+                toggleDialpadVisibility();
+                break;
         }
         return super.onOptionsItemSelected(item);
     }
@@ -1187,6 +1239,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
         }
     }
 
+    @Override
+    protected void onSaveInstanceState(@NonNull @NotNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        int visibility = findViewById(R.id.action_dialpad).getVisibility();
+        System.out.println("visibility onSave = " + visibility);
+        outState.putInt("dialpad_visibility", visibility);
+    }
+
     private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) {
         final Intent currentIntent = getIntent();
         final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);

src/main/java/eu/siacs/conversations/ui/widget/DialpadView.java 🔗

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package eu.siacs.conversations.ui.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
+
+public class DialpadView extends ConstraintLayout implements View.OnClickListener {
+
+    public DialpadView(Context context) {
+        super(context);
+        init();
+    }
+
+    public DialpadView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public DialpadView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.dialpad, this);
+        initViews();
+    }
+
+    private void initViews() {
+        findViewById(R.id.dialpad_1_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_2_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_3_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_4_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_5_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_6_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_7_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_8_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_9_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_0_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_asterisk_holder).setOnClickListener(this);
+        findViewById(R.id.dialpad_pound_holder).setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        /* TODO: this widget doesn't know anything about the RTP Connection,
+            so how to make this widget generic but also able to send touch-tone sounds
+         */
+        System.out.println("v.getTag() = " + v.getTag());
+    }
+
+}

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 
+import org.webrtc.DtmfSender;
 import org.webrtc.EglBase;
 import org.webrtc.IceCandidate;
 import org.webrtc.PeerConnection;
@@ -231,6 +232,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
         }
     }
 
+    //TODO: remove - hack to test dtmfSending
+    public DtmfSender getDtmfSender() {
+        return webRTCWrapper.getDtmfSender();
+    }
+
+    //FIXME: possible implementation
+    public boolean applyDtmfTone(String tone) {
+        return webRTCWrapper.applyDtmfTone(tone);
+    }
+
+
     private void receiveSessionTerminate(final JinglePacket jinglePacket) {
         respondOk(jinglePacket);
         final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();

src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java 🔗

@@ -1,6 +1,7 @@
 package eu.siacs.conversations.xmpp.jingle;
 
 import android.content.Context;
+import android.media.ToneGenerator;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -8,6 +9,7 @@ import android.util.Log;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.Futures;
@@ -25,6 +27,7 @@ import org.webrtc.CandidatePairChangeEvent;
 import org.webrtc.DataChannel;
 import org.webrtc.DefaultVideoDecoderFactory;
 import org.webrtc.DefaultVideoEncoderFactory;
+import org.webrtc.DtmfSender;
 import org.webrtc.EglBase;
 import org.webrtc.IceCandidate;
 import org.webrtc.MediaConstraints;
@@ -47,6 +50,7 @@ import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -83,6 +87,25 @@ public class WebRTCWrapper {
             .add("GT-I9505") // Samsung Galaxy S4 (jfltexx)
             .build();
 
+    private static final int TONE_DURATION = 200;
+    private static final Map<String,Integer> TONE_CODES;
+    static {
+        ImmutableMap.Builder<String,Integer> builder = new ImmutableMap.Builder<>();
+        builder.put("0", ToneGenerator.TONE_DTMF_0);
+        builder.put("1", ToneGenerator.TONE_DTMF_1);
+        builder.put("2", ToneGenerator.TONE_DTMF_2);
+        builder.put("3", ToneGenerator.TONE_DTMF_3);
+        builder.put("4", ToneGenerator.TONE_DTMF_4);
+        builder.put("5", ToneGenerator.TONE_DTMF_5);
+        builder.put("6", ToneGenerator.TONE_DTMF_6);
+        builder.put("7", ToneGenerator.TONE_DTMF_7);
+        builder.put("8", ToneGenerator.TONE_DTMF_8);
+        builder.put("9", ToneGenerator.TONE_DTMF_9);
+        builder.put("*", ToneGenerator.TONE_DTMF_S);
+        builder.put("#", ToneGenerator.TONE_DTMF_P);
+        TONE_CODES = builder.build();
+    }
+
     private static final int CAPTURING_RESOLUTION = 1920;
     private static final int CAPTURING_MAX_FRAME_RATE = 30;
 
@@ -507,6 +530,20 @@ public class WebRTCWrapper {
         return peerConnection;
     }
 
+    //TODO: remove - hack to test dtmfSending
+    public DtmfSender getDtmfSender() {
+        return peerConnection.getSenders().get(0).dtmf();
+    }
+
+    public boolean applyDtmfTone(String tone) {
+        if (toneManager == null || peerConnection.getSenders().isEmpty()) {
+            return false;
+        }
+        peerConnection.getSenders().get(0).dtmf().insertDtmf(tone, TONE_DURATION, 100);
+        toneManager.startTone(TONE_CODES.get(tone), TONE_DURATION);
+        return true;
+    }
+
     void addIceCandidate(IceCandidate iceCandidate) {
         requirePeerConnection().addIceCandidate(iceCandidate);
     }

src/main/res/layout/activity_rtp_session.xml 🔗

@@ -77,6 +77,14 @@
                 android:textAppearance="@style/TextAppearance.Conversations.Title.Monospace"
                 tools:text="01:23" />
 
+            <eu.siacs.conversations.ui.widget.DialpadView
+                layout="@layout/dialpad"
+                android:id="@+id/dialpad"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentBottom="true"
+                android:visibility="gone" />
+
             <com.makeramen.roundedimageview.RoundedImageView
                 android:id="@+id/contact_photo"
                 android:layout_width="@dimen/publish_avatar_size"
@@ -163,7 +171,7 @@
                     app:elevation="4dp"
                     app:fabCustomSize="72dp"
                     app:maxImageSize="36dp"
-                    tools:visibility="visible" />
+                    tools:visibility="gone" />
 
                 <com.google.android.material.floatingactionbutton.FloatingActionButton
                     android:id="@+id/accept_call"
@@ -178,7 +186,7 @@
                     app:elevation="4dp"
                     app:fabCustomSize="72dp"
                     app:maxImageSize="36dp"
-                    tools:visibility="visible" />
+                    tools:visibility="gone" />
 
             </RelativeLayout>
 
@@ -189,7 +197,7 @@
                 android:layout_centerVertical="true"
                 android:layout_margin="@dimen/in_call_fab_margin"
                 android:layout_toStartOf="@+id/end_call"
-                android:visibility="gone"
+                android:visibility="visible"
                 app:backgroundTint="?color_background_primary"
                 app:elevation="4dp"
                 app:fabSize="mini"
@@ -215,7 +223,7 @@
                 android:layout_centerVertical="true"
                 android:layout_margin="@dimen/in_call_fab_margin"
                 android:layout_toEndOf="@+id/end_call"
-                android:visibility="gone"
+                android:visibility="visible"
                 app:backgroundTint="?color_background_primary"
                 app:elevation="4dp"
                 app:fabSize="mini"
@@ -228,7 +236,7 @@
                 android:layout_centerVertical="true"
                 android:layout_margin="@dimen/in_call_fab_margin"
                 android:layout_toEndOf="@+id/in_call_action_right"
-                android:visibility="gone"
+                android:visibility="visible"
                 app:backgroundTint="?color_background_primary"
                 app:elevation="4dp"
                 app:fabSize="mini"

src/main/res/layout/dialpad.xml 🔗

@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/dialpad_holder"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_alignParentBottom="true"
+    android:focusableInTouchMode="true"
+    android:paddingTop="@dimen/medium_margin"
+    tools:ignore="HardcodedText">
+
+    <RelativeLayout
+        android:id="@+id/dialpad_1_holder"
+        android:tag="1"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_2_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_2_holder"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_2_holder"
+        android:focusable="true" >
+
+        <TextView
+            android:id="@+id/dialpad_1"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:text="1" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_2_holder"
+        android:tag="2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/medium_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toTopOf="@+id/dialpad_5_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_3_holder"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_1_holder">
+
+        <TextView
+            android:id="@+id/dialpad_2"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="2" />
+
+        <TextView
+            android:id="@+id/dialpad_2_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_2"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="ABC" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_3_holder"
+        android:tag="3"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginEnd="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_2_holder"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_2_holder"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_2_holder">
+
+        <TextView
+            android:id="@+id/dialpad_3"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="3" />
+
+        <TextView
+            android:id="@+id/dialpad_3_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_3"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="DEF" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_4_holder"
+        android:tag="4"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_5_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_5_holder"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_5_holder">
+
+        <TextView
+            android:id="@+id/dialpad_4"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="4" />
+
+        <TextView
+            android:id="@+id/dialpad_4_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_4"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="GHI" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_5_holder"
+        android:tag="5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/medium_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toTopOf="@+id/dialpad_8_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_6_holder"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_4_holder">
+
+        <TextView
+            android:id="@+id/dialpad_5"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="5" />
+
+        <TextView
+            android:id="@+id/dialpad_5_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_5"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="JKL" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_6_holder"
+        android:tag="6"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginEnd="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_5_holder"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_5_holder"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_5_holder">
+
+        <TextView
+            android:id="@+id/dialpad_6"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="6" />
+
+        <TextView
+            android:id="@+id/dialpad_6_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_6"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="MNO" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_7_holder"
+        android:tag="7"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_8_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_8_holder"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_8_holder">
+
+        <TextView
+            android:id="@+id/dialpad_7"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="7" />
+
+        <TextView
+            android:id="@+id/dialpad_7_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_7"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="PQRS" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_8_holder"
+        android:tag="8"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/medium_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toTopOf="@+id/dialpad_0_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_9_holder"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_7_holder">
+
+        <TextView
+            android:id="@+id/dialpad_8"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="8" />
+
+        <TextView
+            android:id="@+id/dialpad_8_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_8"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="TUV" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_9_holder"
+        android:tag="9"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginEnd="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_8_holder"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_8_holder"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_8_holder">
+
+        <TextView
+            android:id="@+id/dialpad_9"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="9" />
+
+        <TextView
+            android:id="@+id/dialpad_9_letters"
+            style="@style/DialpadLetterStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/dialpad_9"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/medium_margin"
+            android:gravity="center_horizontal"
+            android:text="WXYZ" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_asterisk_holder"
+        android:tag="*"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_0_holder"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_0_holder"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_0_holder">
+
+        <TextView
+            android:id="@+id/dialpad_asterisk"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:text="*" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_0_holder"
+        android:tag="0"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/dialpad_pound_holder"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_asterisk_holder">
+
+        <TextView
+            android:id="@+id/dialpad_0"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="0" />
+
+        <TextView
+            android:id="@+id/dialpad_plus"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/dialpad_0"
+            android:layout_alignBottom="@+id/dialpad_0"
+            android:layout_centerHorizontal="true"
+            android:layout_toEndOf="@+id/dialpad_0"
+            android:gravity="center"
+            android:paddingStart="@dimen/small_margin"
+            android:paddingTop="@dimen/small_margin"
+            android:text="+"
+            android:textSize="@dimen/actionbar_text_size" />
+
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/dialpad_pound_holder"
+        android:tag="#"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginEnd="@dimen/activity_margin"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        app:layout_constraintBottom_toBottomOf="@+id/dialpad_0_holder"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/dialpad_0_holder"
+        app:layout_constraintTop_toTopOf="@+id/dialpad_0_holder">
+
+        <TextView
+            android:id="@+id/dialpad_pound"
+            style="@style/DialpadNumberStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_marginEnd="@dimen/activity_margin"
+            android:text="#" />
+    </RelativeLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

src/main/res/menu/activity_rtp_session.xml 🔗

@@ -12,5 +12,11 @@
         android:id="@+id/action_goto_chat"
         android:icon="?attr/icon_goto_chat"
         android:title="@string/switch_to_conversation"
+        app:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/action_dialpad"
+        android:icon="?attr/icon_new"
+        android:title="@string/action_dialpad"
         app:showAsAction="always" />
+
 </menu>

src/main/res/values/dimens.xml 🔗

@@ -44,4 +44,11 @@
     <dimen name="local_video_preview_height">128dp</dimen>
     <dimen name="local_video_preview_width">96dp</dimen>
     <dimen name="rtp_session_duration_top_margin">24dp</dimen>
+
+    <dimen name="dialpad_text_size">30sp</dimen>
+    <dimen name="smaller_text_size">12sp</dimen>
+    <dimen name="medium_margin">8dp</dimen>
+    <dimen name="activity_margin">16dp</dimen>
+    <dimen name="small_margin">4dp</dimen>
+    <dimen name="actionbar_text_size">20sp</dimen>
 </resources>

src/main/res/values/strings.xml 🔗

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="action_settings">Settings</string>
+    <string name="action_dialpad">Dialpad</string>
     <string name="action_add">New conversation</string>
     <string name="action_accounts">Manage accounts</string>
     <string name="action_account">Manage account</string>

src/main/res/values/styles.xml 🔗

@@ -159,4 +159,14 @@
     <style name="TextAppearance.Conversations.Body1.Secondary.OnDark" parent="TextAppearance.Conversations.Body1">
         <item name="android:textColor">@color/white70</item>
     </style>
-</resources>
+
+    <style name="DialpadNumberStyle">
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:textSize">@dimen/dialpad_text_size</item>
+    </style>
+
+    <style name="DialpadLetterStyle">
+        <item name="android:textSize">@dimen/smaller_text_size</item>
+        <item name="android:alpha">0.8</item>
+    </style>
+</resources>