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>