Implement NdnPing client

Update jNDN to 0.14 and fix a small problem Crypto++ fixed in upstream.

Change-Id: I4697489dcc7f74f1d7c40e196f5f983e4bd3b1ef
Refs: #3459
diff --git a/app/src/main/java/net/named_data/nfd/MainActivity.java b/app/src/main/java/net/named_data/nfd/MainActivity.java
index 05b1e72..4152545 100644
--- a/app/src/main/java/net/named_data/nfd/MainActivity.java
+++ b/app/src/main/java/net/named_data/nfd/MainActivity.java
@@ -44,7 +44,6 @@
                FaceListFragment.Callbacks,
                RouteListFragment.Callbacks
 {
-
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
@@ -65,6 +64,8 @@
                                               DRAWER_ITEM_FACES));
       items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_routes, 0,
                                               DRAWER_ITEM_ROUTES));
+      items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_ping, 0,
+                                              DRAWER_ITEM_PING));
       //    items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_strategies, 0,
       //                                            DRAWER_ITEM_STRATEGIES));
       items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_logcat, 0,
@@ -147,6 +148,9 @@
         case DRAWER_ITEM_ROUTES:
           fragment = RouteListFragment.newInstance();
           break;
+        case DRAWER_ITEM_PING:
+          fragment = PingClientFragment.newInstance();
+          break;
         // TODO: Placeholders; Fill these in when their fragments have been created
         //    case DRAWER_ITEM_STRATEGIES:
         //      break;
@@ -195,6 +199,7 @@
   public static final int DRAWER_ITEM_GENERAL = 1;
   public static final int DRAWER_ITEM_FACES = 2;
   public static final int DRAWER_ITEM_ROUTES = 3;
-  public static final int DRAWER_ITEM_STRATEGIES = 4;
+  public static final int DRAWER_ITEM_PING = 4;
+  //public static final int DRAWER_ITEM_STRATEGIES = 4;
   public static final int DRAWER_ITEM_LOGCAT = 5;
 }
diff --git a/app/src/main/java/net/named_data/nfd/PingClient.java b/app/src/main/java/net/named_data/nfd/PingClient.java
new file mode 100644
index 0000000..70abdfc
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/PingClient.java
@@ -0,0 +1,288 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2016 Regents of the University of California
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ *
+ * NFD Android 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.
+ *
+ * NFD Android 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
+ * NFD Android, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.named_data.nfd;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.NetworkNack;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnNetworkNack;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.nfd.utils.G;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+class PingClient {
+
+  interface PingClientListener {
+    void onPingResponse(final String prefix, final long seq, final double elapsedTime);
+
+    void onPingTimeout(final String prefix, final long seq);
+
+    void onPingNack(final String prefix, final long seq, final NetworkNack.Reason reason);
+
+    void onCalcStatistics(final String msg);
+
+    void onPingFinish();
+  }
+
+  private class PingStats implements Serializable {
+    int dataCount = 0;
+    int timeoutCount = 0;
+    int nackCount = 0;
+
+    double timeSum = 0;
+    double timeMin = Double.MAX_VALUE;
+    double timeMax = 0;
+    double timeSquareSum = 0;
+  }
+
+  private PingStats m_pingStats;
+  private PingClientListener m_listener;
+  private String m_pingPrefix;
+  private long m_pingSeq;
+  private AtomicBoolean m_isRunning = new AtomicBoolean(false);
+  private Handler m_handler;
+  private Face m_face;
+  private HandlerThread m_thread;
+
+  /////////////////////////////////////////////////////////////////////////
+
+  PingClient(String pingPrefix)
+  {
+    m_pingPrefix = pingPrefix;
+    m_pingStats = new PingStats();
+  }
+
+  PingClient(String pingPrefix, Serializable savedStats)
+  {
+    m_pingPrefix = pingPrefix;
+    m_pingStats = (PingStats)savedStats;
+  }
+
+  Serializable getState() {
+    return m_pingStats;
+  }
+
+  void setListener(PingClientListener listener) {
+    m_listener = listener;
+  }
+
+  public void start() {
+    m_thread = new HandlerThread("PingClientThread");
+    m_thread.start();
+    m_handler = new Handler(m_thread.getLooper());
+    m_handler.post(new Runnable() {
+      @Override
+      public void run()
+      {
+        initiate();
+      }
+    });
+  }
+
+  public void stop() {
+    m_isRunning.set(false);
+    // thread will be killed shorty, but don't wait to so we not going to block UI
+  }
+
+  /////////////////////////////////////////////////////////////////////////
+
+  private void notifyPingResponse(String prefix, long seq, double elapsedTime) {
+    if (m_listener == null) return;
+    m_listener.onPingResponse(prefix, seq, elapsedTime);
+  }
+
+  private void notifyPingTimeout(String prefix, long seq) {
+    if (m_listener == null) return;
+    m_listener.onPingTimeout(prefix, seq);
+  }
+
+  private void notifyPingNack(String prefix, long seq, NetworkNack.Reason reason) {
+    if (m_listener == null) return;
+    m_listener.onPingNack(prefix, seq, reason);
+  }
+
+  private void onCalcStatistics(String msg) {
+    if (m_listener == null) return;
+    m_listener.onCalcStatistics(msg);
+  }
+
+  private void notifyPingFinish() {
+    if (m_listener == null) return;
+    m_listener.onPingFinish();
+  }
+
+  private void calculateStatistics() {
+    int count = m_pingStats.dataCount + m_pingStats.timeoutCount + m_pingStats.nackCount;
+    if (count == 0) {
+      return;
+    }
+    StringBuilder stat = new StringBuilder();
+    stat
+      .append("--- ").append(m_pingPrefix).append(" ping statistics ---")
+      .append("\n")
+      .append(count).append(" packets transmitted, ")
+      .append(m_pingStats.dataCount).append(" received, ")
+      .append(m_pingStats.timeoutCount * 100 / count).append("% packet loss, ")
+      .append("time ")
+      .append(String.format(Locale.getDefault(), "%.3f", m_pingStats.timeSum))
+      .append(" ms");
+
+    if (m_pingStats.dataCount != 0 && m_pingStats.timeSum > 0) {
+      double avg = m_pingStats.timeSum * 1.0 / m_pingStats.dataCount;
+      double mdev = Math.sqrt(m_pingStats.timeSquareSum / m_pingStats.dataCount - avg * avg);
+      stat
+        .append("\n")
+        .append("rtt min/avg/max/mdev = ")
+        .append(String.format(Locale.getDefault(), "%.3f", m_pingStats.timeMin))
+        .append("/")
+        .append(String.format(Locale.getDefault(), "%.4f", avg))
+        .append("/")
+        .append(String.format(Locale.getDefault(), "%.3f", m_pingStats.timeMax))
+        .append("/")
+        .append(String.format(Locale.getDefault(), "%.4f", mdev)).append(" ms");
+    }
+
+    onCalcStatistics(stat.toString());
+  }
+
+  private void initiate() {
+    G.Log("INITIATE ping");
+    m_isRunning.set(true);
+    m_pingSeq = Math.abs(new Random().nextLong());
+
+    m_face = new Face();
+    requestNextPing(0);
+    processEvents();
+  }
+
+  private void processEvents() {
+    try {
+      if (m_isRunning.get()) {
+        m_face.processEvents();
+        m_handler.postDelayed(new Runnable() {
+          @Override
+          public void run()
+          {
+            processEvents();
+          }
+        }, 100);
+      }
+      else {
+        terminate();
+      }
+    }
+    catch (IOException e) {
+      G.Log("Face error: " + e.getMessage());
+    }
+    catch (EncodingException e) {
+      G.Log("Encoding error: " + e.getMessage());
+    }
+  }
+
+  private void terminate() {
+    G.Log("TERMINATE ping, " + m_face.hashCode());
+    calculateStatistics();
+    notifyPingFinish();
+
+    m_face.shutdown();
+    m_face = null;
+    m_isRunning.set(false);
+    m_thread.quit();
+  }
+
+  private void requestNextPing(long delay) {
+    m_handler.postDelayed(new Runnable() {
+      @Override
+      public void run()
+      {
+        newPing();
+      }
+    }, delay);
+  }
+
+  private void newPing()
+  {
+    if (m_face == null) {
+      G.Log("Requested new ping, but face is not available");
+      return;
+    }
+    Name name = new Name(m_pingPrefix + "/ping/" + m_pingSeq++);
+    Interest interest = new Interest(name, 1000);
+    interest.setMustBeFresh(true);
+
+    final long startTime = System.nanoTime();
+    try {
+      m_face.expressInterest(interest,
+                             new OnData() {
+                               @Override
+                               public void onData(Interest interest, Data data)
+                               {
+                                 double elapsedTime = (System.nanoTime() - startTime) / 1000000.0;
+                                 ++m_pingStats.dataCount;
+                                 m_pingStats.timeSum += elapsedTime;
+                                 m_pingStats.timeSquareSum += elapsedTime * elapsedTime;
+                                 if (elapsedTime > m_pingStats.timeMax)
+                                   m_pingStats.timeMax = elapsedTime;
+                                 if (elapsedTime < m_pingStats.timeMin)
+                                   m_pingStats.timeMin = elapsedTime;
+
+                                 // Send a result to Screen
+                                 notifyPingResponse(m_pingPrefix, m_pingSeq, elapsedTime);
+                                 requestNextPing(1000);
+                               }
+                             },
+                             new OnTimeout() {
+                               @Override
+                               public void onTimeout(Interest interest)
+                               {
+                                 ++m_pingStats.timeoutCount;
+
+                                 notifyPingTimeout(m_pingPrefix, m_pingSeq);
+                                 requestNextPing(0);
+                               }
+                             },
+                             new OnNetworkNack() {
+                               @Override
+                               public void onNetworkNack(Interest interest, NetworkNack networkNack)
+                               {
+                                 ++m_pingStats.nackCount;
+
+                                 notifyPingNack(m_pingPrefix, m_pingSeq, networkNack.getReason());
+                                 requestNextPing(1000);
+                               }
+                             });
+    }
+    catch (IOException e) {
+      G.Log("Error expressing the interest: " + e.getMessage());
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/PingClientFragment.java b/app/src/main/java/net/named_data/nfd/PingClientFragment.java
new file mode 100644
index 0000000..12f06f6
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/PingClientFragment.java
@@ -0,0 +1,313 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2016 Regents of the University of California
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ *
+ * NFD Android 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.
+ *
+ * NFD Android 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
+ * NFD Android, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.named_data.nfd;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import net.named_data.jndn.NetworkNack;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class PingClientFragment extends Fragment implements PingClient.PingClientListener {
+
+  private PingClient m_client;
+
+  /**
+   *  View holder object for holding the output.
+   */
+  private static class PingResultEntryViewHolder {
+    public TextView pingResultTextView;
+  }
+
+  /** ListView for displaying ping output in */
+  private ListView m_pingResultListView;
+
+  private EditText m_pingNameEditText;
+
+  private Button m_pingStartButton;
+  private boolean m_isStartState = true;
+
+  /** Customized ListAdapter for controlling ping output */
+  private PingResultListAdapter m_pingResultListAdapter;
+
+  private final String TAG_PING_STATUS = "PingStatus";
+  private final String TAG_PING_NAME = "PingName";
+  private final String TAG_PING_DATA = "PingData";
+  private final String TAG_PING_STATE = "PingState";
+
+  /////////////////
+
+  public static PingClientFragment newInstance() {
+    return new PingClientFragment();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+  }
+
+  @Override
+  public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                           Bundle savedInstanceState) {
+    // Inflate the layout for this fragment
+    View v = inflater.inflate(R.layout.fragment_ping_client, container, false);
+
+    // Get UI Elements
+    m_pingResultListView = (ListView) v.findViewById(R.id.pingResult);
+
+    m_pingNameEditText = (EditText) v.findViewById(R.id.pingName);
+    m_pingStartButton = (Button) v.findViewById(R.id.pingStart);
+    m_pingStartButton.setOnClickListener(new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+          InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+          imm.hideSoftInputFromWindow(m_pingNameEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
+          if (m_isStartState) {
+            setButtonState(false);
+            String pingPrefix = m_pingNameEditText.getText().toString();
+
+            m_pingResultListAdapter.clearMessages();
+            m_pingResultListAdapter.addMessage("PING " + pingPrefix);
+            m_pingResultListView.setSelection(m_pingResultListAdapter.getCount() - 1);
+
+            m_client = new PingClient(pingPrefix);
+            m_client.setListener(PingClientFragment.this);
+            m_client.start();
+          } else {
+            setButtonState(true);
+            if (m_client != null) {
+              m_client.stop();
+            }
+          }
+        }
+      });
+
+    return v;
+  }
+
+  private void setButtonState(boolean isStartState)
+  {
+    if (isStartState) {
+      m_pingStartButton.setText(R.string.start);
+    }
+    else {
+      m_pingStartButton.setText(R.string.stop);
+    }
+    m_isStartState = isStartState;
+  }
+
+  @Override
+  public void onActivityCreated(@Nullable Bundle savedInstanceState)
+  {
+    super.onActivityCreated(savedInstanceState);
+    if (m_pingResultListAdapter == null) {
+      m_pingResultListAdapter = new PingResultListAdapter(getActivity());
+    }
+    m_pingResultListView.setAdapter(m_pingResultListAdapter);
+
+    if (savedInstanceState != null) {
+      setButtonState(savedInstanceState.getBoolean(TAG_PING_STATUS));
+      m_pingNameEditText.setText(savedInstanceState.getString(TAG_PING_NAME));
+      m_pingResultListAdapter.setData(savedInstanceState.getStringArrayList(TAG_PING_DATA));
+
+      Serializable state = savedInstanceState.getSerializable(TAG_PING_STATE);
+      if (!m_isStartState && state != null) {
+        m_client = new PingClient(m_pingNameEditText.getText().toString(), state);
+      }
+    }
+  }
+
+  @Override
+  public void onStop() {
+    super.onStop();
+    if (m_client != null) {
+      m_client.setListener(null);
+      m_client.stop();
+    }
+  }
+
+  @Override
+  public void onSaveInstanceState(Bundle outState)
+  {
+    super.onSaveInstanceState(outState);
+    outState.putBoolean(TAG_PING_STATUS, m_isStartState);
+    outState.putString(TAG_PING_NAME, m_pingNameEditText.getText().toString());
+    outState.putStringArrayList(TAG_PING_DATA, m_pingResultListAdapter.m_data);
+    if (!m_isStartState && m_client != null) {
+      outState.putSerializable(TAG_PING_STATE, m_client.getState());
+    }
+  }
+
+  @Override
+  public void onStart() {
+    super.onStart();
+    if (m_client != null) {
+      m_client.setListener(this);
+      m_client.start();
+    }
+  }
+
+  @Override
+  public void onPingResponse(final String prefix, final long seq, final double elapsedTime) {
+    this.getActivity().runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          m_pingResultListAdapter.addMessage("content from " + prefix + ": seq=" + seq
+                                             + " time=" + String.format(Locale.getDefault(), "%.3f", elapsedTime) + " ms");
+          m_pingResultListView.setSelection(m_pingResultListAdapter.getCount() - 1);
+        }
+      });
+  }
+
+  @Override
+  public void onPingTimeout(final String prefix, final long seq) {
+    this.getActivity().runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          m_pingResultListAdapter.addMessage("timeout from " + prefix + ": seq=" + seq);
+          m_pingResultListView.setSelection(m_pingResultListAdapter.getCount() - 1);
+        }
+      });
+  }
+
+  @Override
+  public void onPingNack(final String prefix, final long seq, final NetworkNack.Reason reason) {
+    this.getActivity().runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          m_pingResultListAdapter.addMessage("NACK from " + prefix + ": seq=" + seq
+                                             + " reason=" + reason);
+          m_pingResultListView.setSelection(m_pingResultListAdapter.getCount() - 1);
+        }
+      });
+  }
+
+  @Override
+  public void onCalcStatistics(final String msg) {
+    this.getActivity().runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          m_pingResultListAdapter.addMessage(msg);
+          m_pingResultListView.setSelection(m_pingResultListAdapter.getCount() - 1);
+        }
+      });
+  }
+
+  @Override
+  public void onPingFinish() {
+    this.getActivity().runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          m_pingStartButton.setTag(null);
+          m_pingStartButton.setText(R.string.start);
+        }
+      });
+  }
+
+  ////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private class PingResultListAdapter extends BaseAdapter {
+
+    /**
+     * Create a ListView compatible adapter with an
+     * upper bound on the maximum number of entries that will
+     * be displayed in the ListView.
+     */
+    PingResultListAdapter(Context context) {
+      m_data = new ArrayList<>();
+      m_layoutInflater = LayoutInflater.from(context);
+    }
+
+    /**
+     * Add a message to be displayed in the ping result's list view.
+     *
+     * @param message Message to be added to the underlying data store
+     *                and displayed on thi UI.
+     */
+    void addMessage(String message) {
+      m_data.add(message);
+      notifyDataSetChanged();
+    }
+
+    /**
+     * Convenience method to clear all messages from the underlying
+     * data store and update the UI.
+     */
+    void clearMessages() {
+      m_data.clear();
+      this.notifyDataSetChanged();
+    }
+
+    void setData(ArrayList<String> data) {
+      m_data.clear();
+      m_data.addAll(data);
+      this.notifyDataSetChanged();
+    }
+
+    @Override
+    public int getCount() {
+      return m_data.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+      return m_data.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+      return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      PingResultEntryViewHolder holder;
+
+      if (convertView == null) {
+        holder = new PingResultEntryViewHolder();
+        convertView = m_layoutInflater.inflate(R.layout.list_item_ping_result, null);
+        convertView.setTag(holder);
+        holder.pingResultTextView = (TextView) convertView.findViewById(R.id.ping_result);
+      } else {
+        holder = (PingResultEntryViewHolder) convertView.getTag();
+      }
+
+      holder.pingResultTextView.setText(m_data.get(position));
+      return convertView;
+    }
+
+    private final ArrayList<String> m_data;
+    private final LayoutInflater m_layoutInflater;
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/RouteListFragment.java b/app/src/main/java/net/named_data/nfd/RouteListFragment.java
index 1b666cc..69854a7 100644
--- a/app/src/main/java/net/named_data/nfd/RouteListFragment.java
+++ b/app/src/main/java/net/named_data/nfd/RouteListFragment.java
@@ -72,11 +72,11 @@
   }
 
   @Override
-  public void onAttach(Activity activity)
+  public void onAttach(Context context)
   {
-    super.onAttach(activity);
+    super.onAttach(context);
     try {
-      m_callbacks = (Callbacks)activity;
+      m_callbacks = (Callbacks)context;
     } catch (Exception e) {
       G.Log("Hosting activity must implement this fragment's callbacks: " + e);
     }
diff --git a/app/src/main/jni/cryptopp b/app/src/main/jni/cryptopp
index 3a081d5..5ac4019 160000
--- a/app/src/main/jni/cryptopp
+++ b/app/src/main/jni/cryptopp
@@ -1 +1 @@
-Subproject commit 3a081d5e25ad1c54f27a104f8e0f01c7e2ff4145
+Subproject commit 5ac4019f0893bae2e40c2698427afc8ea374c1d3
diff --git a/app/src/main/res/layout/fragment_ping_client.xml b/app/src/main/res/layout/fragment_ping_client.xml
new file mode 100644
index 0000000..82760b3
--- /dev/null
+++ b/app/src/main/res/layout/fragment_ping_client.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/default_linear_layout_padding"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="10dp"
+        android:paddingBottom="10dp"
+        android:orientation="horizontal">
+
+        <EditText
+            android:id="@+id/pingName"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="10"
+            android:hint="@string/ping_client_prefix_hint"/>
+
+        <Button
+            android:id="@+id/pingStart"
+            style="?android:attr/buttonStyleSmall"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/start" />
+
+    </LinearLayout>
+
+    <ListView
+        android:id="@+id/pingResult"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    </ListView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_ping_result.xml b/app/src/main/res/layout/list_item_ping_result.xml
new file mode 100644
index 0000000..e487ff5
--- /dev/null
+++ b/app/src/main/res/layout/list_item_ping_result.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/default_linear_layout_padding"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/ping_result"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5691bdd..d25909a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,6 @@
 <resources>
+    <string name="start">Start</string>
+    <string name="stop">Stop</string>
     <string name="app_name">NFD</string>
     <string name="service_name">NFD Service</string>
     <string name="action_settings">Settings</string>
@@ -15,6 +17,7 @@
     <string name="drawer_item_general">General</string>
     <string name="drawer_item_faces">Faces</string>
     <string name="drawer_item_routes">Routes</string>
+    <string name="drawer_item_ping">Ping</string>
     <string name="drawer_item_strategies">Strategies</string>
     <string name="drawer_item_logcat">Logcat</string>
     <string name="menu_item_delete_setting_item">Delete</string>
@@ -72,4 +75,5 @@
     <string name="fragment_route_details_title">Route Details</string>
     <string name="fragment_route_details_next_hops">List of next hops</string>
     <string name="fragment_route_route_name_title">Route Name</string>
+    <string name="ping_client_prefix_hint">Prefix (e.g., /ndn/edu/arizona)</string>
 </resources>