Example integration with WiFiDirect

Change-Id: Id65530c43d67894b46ce1aa0d523e6e99e621023
Refs:3939
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc07744..948b73b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,9 @@
     <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application
@@ -43,6 +46,7 @@
             </intent-filter>
         </receiver>
 
+        <service android:name="net.named_data.nfd.wifidirect.service.WDBroadcastReceiverService"/>
     </application>
 
 </manifest>
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 4152545..ee19928 100644
--- a/app/src/main/java/net/named_data/nfd/MainActivity.java
+++ b/app/src/main/java/net/named_data/nfd/MainActivity.java
@@ -1,6 +1,6 @@
 /* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2015 Regents of the University of California
+ * Copyright (c) 2015-2017 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.
@@ -32,6 +32,7 @@
 import com.intel.jndn.management.types.RibEntry;
 
 import net.named_data.nfd.utils.G;
+import net.named_data.nfd.wifidirect.utils.NDNController;
 
 import java.util.ArrayList;
 
@@ -44,6 +45,7 @@
                FaceListFragment.Callbacks,
                RouteListFragment.Callbacks
 {
+
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
@@ -70,6 +72,11 @@
       //                                            DRAWER_ITEM_STRATEGIES));
       items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_logcat, 0,
                                               DRAWER_ITEM_LOGCAT));
+      items.add(new DrawerFragment.DrawerItem(R.string.drawer_item_wifidirect, 0, DRAWER_ITEM_WIFIDIRECT));
+
+      // TODO here we are preloading the NDNController singleton to avoid UI slowdown
+      // it is due to building a test keychain: See NDNController.getInstance()
+      NDNController.getInstance();
 
       m_drawerFragment = DrawerFragment.newInstance(items);
 
@@ -157,6 +164,9 @@
         case DRAWER_ITEM_LOGCAT:
           fragment = LogcatFragment.newInstance();
           break;
+        case DRAWER_ITEM_WIFIDIRECT:
+          fragment = WiFiDirectFragment.newInstance();
+          break;
         default:
           // Invalid; Nothing else needs to be done
           return;
@@ -202,4 +212,5 @@
   public static final int DRAWER_ITEM_PING = 4;
   //public static final int DRAWER_ITEM_STRATEGIES = 4;
   public static final int DRAWER_ITEM_LOGCAT = 5;
+  public static final int DRAWER_ITEM_WIFIDIRECT = 6;
 }
diff --git a/app/src/main/java/net/named_data/nfd/MainFragment.java b/app/src/main/java/net/named_data/nfd/MainFragment.java
index 898b504..72df064 100644
--- a/app/src/main/java/net/named_data/nfd/MainFragment.java
+++ b/app/src/main/java/net/named_data/nfd/MainFragment.java
@@ -1,18 +1,18 @@
 /* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2015 Regents of the University of California
- *
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
  * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
  * See AUTHORS.md for complete list of NFD Android authors and contributors.
- *
+ * <p>
  * 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.
- *
+ * <p>
  * 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.
- *
+ * <p>
  * 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/>.
  */
@@ -47,6 +47,7 @@
 import net.named_data.nfd.service.NfdService;
 import net.named_data.nfd.utils.G;
 import net.named_data.nfd.utils.NfdcHelper;
+import net.named_data.nfd.wifidirect.utils.NDNController;
 
 import org.joda.time.Period;
 import org.joda.time.format.PeriodFormat;
@@ -59,8 +60,7 @@
   }
 
   @Override
-  public void onCreate(Bundle savedInstanceState)
-  {
+  public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     m_handler = new Handler();
   }
@@ -68,51 +68,47 @@
   @Override
   public View onCreateView(LayoutInflater inflater,
                            @Nullable ViewGroup container,
-                           @Nullable Bundle savedInstanceState)
-  {
+                           @Nullable Bundle savedInstanceState) {
     @SuppressLint("InflateParams")
-    View v =  inflater.inflate(R.layout.fragment_main, null);
-
-    m_nfdStartStopSwitch = (Switch)v.findViewById(R.id.nfd_start_stop_switch);
+    View v = inflater.inflate(R.layout.fragment_main, null);
+    m_nfdStartStopSwitch = (Switch) v.findViewById(R.id.nfd_start_stop_switch);
     m_nfdStartStopSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
       @Override
-      public void onCheckedChanged(CompoundButton compoundButton, boolean isOn)
-      {
+      public void onCheckedChanged(CompoundButton compoundButton, boolean isOn) {
         m_sharedPreferences.edit()
           .putBoolean(PREF_NFD_SERVICE_STATUS, isOn)
           .apply();
 
         if (isOn) {
           startNfdService();
-        }
-        else {
+        } else {
+          NDNController.getInstance().stop(); //stop wifi direct
           stopNfdService();
         }
       }
     });
 
-    m_nfdStatusView = (ViewGroup)v.findViewById(R.id.status_view);
+    m_nfdStatusView = (ViewGroup) v.findViewById(R.id.status_view);
     m_nfdStatusView.setVisibility(View.GONE);
-    m_versionView = (TextView)v.findViewById(R.id.version);
-    m_uptimeView = (TextView)v.findViewById(R.id.uptime);
-    m_nameTreeEntriesView = (TextView)v.findViewById(R.id.name_tree_entries);
-    m_fibEntriesView = (TextView)v.findViewById(R.id.fib_entries);
-    m_pitEntriesView = (TextView)v.findViewById(R.id.pit_entries);
-    m_measurementEntriesView = (TextView)v.findViewById(R.id.measurement_entries);
-    m_csEntriesView = (TextView)v.findViewById(R.id.cs_entries);
-    m_inInterestsView = (TextView)v.findViewById(R.id.in_interests);
-    m_outInterestsView = (TextView)v.findViewById(R.id.out_interests);
-    m_inDataView = (TextView)v.findViewById(R.id.in_data);
-    m_outDataView = (TextView)v.findViewById(R.id.out_data);
-    m_inNacksView = (TextView)v.findViewById(R.id.in_nacks);
-    m_outNacksView = (TextView)v.findViewById(R.id.out_nacks);
+    m_versionView = (TextView) v.findViewById(R.id.version);
+    m_uptimeView = (TextView) v.findViewById(R.id.uptime);
+    m_nameTreeEntriesView = (TextView) v.findViewById(R.id.name_tree_entries);
+    m_fibEntriesView = (TextView) v.findViewById(R.id.fib_entries);
+    m_pitEntriesView = (TextView) v.findViewById(R.id.pit_entries);
+    m_measurementEntriesView = (TextView) v.findViewById(R.id.measurement_entries);
+    m_csEntriesView = (TextView) v.findViewById(R.id.cs_entries);
+    m_inInterestsView = (TextView) v.findViewById(R.id.in_interests);
+    m_outInterestsView = (TextView) v.findViewById(R.id.out_interests);
+    m_inDataView = (TextView) v.findViewById(R.id.in_data);
+    m_outDataView = (TextView) v.findViewById(R.id.out_data);
+    m_inNacksView = (TextView) v.findViewById(R.id.in_nacks);
+    m_outNacksView = (TextView) v.findViewById(R.id.out_nacks);
 
     return v;
   }
 
   @Override
-  public void onActivityCreated(@Nullable Bundle savedInstanceState)
-  {
+  public void onActivityCreated(@Nullable Bundle savedInstanceState) {
     super.onActivityCreated(savedInstanceState);
     m_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
   }
@@ -143,7 +139,7 @@
     if (!m_isNfdServiceConnected) {
       // Bind to Service
       getActivity().bindService(new Intent(getActivity(), NfdService.class),
-                                           m_ServiceConnection, Context.BIND_AUTO_CREATE);
+        m_ServiceConnection, Context.BIND_AUTO_CREATE);
       G.Log("MainFragment::bindNfdService()");
     }
   }
@@ -228,7 +224,7 @@
 
   /**
    * Client Message Handler.
-   *
+   * <p>
    * This handler is used to handle messages that are being sent back
    * from the NfdService to the current application.
    */
@@ -297,15 +293,14 @@
 
   /**
    * Attempt to reconnect to the NfdService.
-   *
+   * <p>
    * This method attempts to reconnect the application to the NfdService
    * when the NfdService has been killed (either by the user or by the OS).
    */
   private Runnable m_retryConnectionToNfdService = new Runnable() {
     @Override
     public void
-    run()
-    {
+    run() {
       G.Log("Retrying connection to NFD Service ...");
       bindNfdService();
     }
@@ -318,15 +313,13 @@
      */
     @Override
     protected ForwarderStatus
-    doInBackground(Void... voids)
-    {
+    doInBackground(Void... voids) {
       try {
         NfdcHelper nfdcHelper = new NfdcHelper();
         ForwarderStatus fs = nfdcHelper.generalStatus();
         nfdcHelper.shutdown();
         return fs;
-      }
-      catch (Exception e) {
+      } catch (Exception e) {
         G.Log("Error communicating with NFD (" + e.getMessage() + ")");
         return null;
       }
@@ -334,13 +327,11 @@
 
     @Override
     protected void
-    onPostExecute(ForwarderStatus fs)
-    {
+    onPostExecute(ForwarderStatus fs) {
       if (fs == null) {
         // when failed, try after 0.5 seconds
         m_handler.postDelayed(m_statusUpdateRunnable, 500);
-      }
-      else {
+      } else {
         m_versionView.setText(fs.getNfdVersion());
         m_uptimeView.setText(PeriodFormat.getDefault().print(new Period(
           fs.getCurrentTimestamp() - fs.getStartTimestamp())));
@@ -371,19 +362,29 @@
 
   //////////////////////////////////////////////////////////////////////////////
 
-  /** Button that starts and stops the NFD */
+  /**
+   * Button that starts and stops the NFD
+   */
   private Switch m_nfdStartStopSwitch;
 
-  /** Flag that marks that application is connected to the NfdService */
+  /**
+   * Flag that marks that application is connected to the NfdService
+   */
   private boolean m_isNfdServiceConnected = false;
 
-  /** Client Message Handler */
+  /**
+   * Client Message Handler
+   */
   private final Messenger m_clientMessenger = new Messenger(new ClientHandler());
 
-  /** Messenger connection to NfdService */
+  /**
+   * Messenger connection to NfdService
+   */
   private Messenger m_nfdServiceMessenger = null;
 
-  /** ListView holding NFD status information */
+  /**
+   * ListView holding NFD status information
+   */
   private ViewGroup m_nfdStatusView;
 
   private TextView m_versionView;
@@ -403,8 +404,7 @@
   private Handler m_handler;
   private Runnable m_statusUpdateRunnable = new Runnable() {
     @Override
-    public void run()
-    {
+    public void run() {
       new StatusUpdateTask().execute();
     }
   };
@@ -412,4 +412,4 @@
   private SharedPreferences m_sharedPreferences;
 
   private static final String PREF_NFD_SERVICE_STATUS = "NFD_SERVICE_STATUS";
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/named_data/nfd/WiFiDirectFragment.java b/app/src/main/java/net/named_data/nfd/WiFiDirectFragment.java
new file mode 100644
index 0000000..2d67c22
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/WiFiDirectFragment.java
@@ -0,0 +1,472 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 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.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.ListView;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.named_data.nfd.wifidirect.model.Peer;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ProgressDialog.show;
+import static net.named_data.nfd.wifidirect.utils.NDNController.myAddress;
+import static net.named_data.nfd.wifidirect.utils.NDNController.myDeviceName;
+
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Activities that contain this fragment must implement the
+ * Use the {@link WiFiDirectFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class WiFiDirectFragment extends Fragment {
+
+  private static final String TAG = "WiFiDirectFragment";
+
+  public WiFiDirectFragment() {
+    // Required empty public constructor
+  }
+
+  /**
+   * Use this factory method to create a new instance of
+   * this fragment using the provided parameters.
+   *
+   * @return A new instance of fragment WiFiDirectFragment.
+   */
+  public static WiFiDirectFragment newInstance() {
+    return new WiFiDirectFragment();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    m_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
+    m_handler = new Handler();
+  }
+
+  @Override
+  public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                           Bundle savedInstanceState) {
+    // Inflate the layout for this fragment
+    View view = inflater.inflate(R.layout.fragment_wifidirect, container, false);
+
+    // init UI elements
+    m_wdGroupConnStatus = (TextView) view.findViewById(R.id.wd_group_conn_status_textview);
+    m_wdIpAddress = (TextView) view.findViewById(R.id.wd_ip_address_textview);
+    m_wdDeviceName = (TextView) view.findViewById(R.id.wd_this_device_name_textview);
+    m_wdIsGroupOwner = (TextView) view.findViewById(R.id.wd_group_owner_textview);
+    m_wdSwitch = (Switch) view.findViewById(R.id.wd_switch);
+
+    if (m_sharedPreferences.getBoolean(PREF_WIFIDIRECT_STATUS, false)) {
+      m_wdSwitch.setChecked(true);
+      startNDNOverWifiDirect();
+      startUiUpdateLoop();
+    } else {
+      // the button was off, make any desired UI changes
+      m_wdGroupConnStatus.setText("");
+    }
+
+    m_wdSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+      @Override
+      public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        // store state of switch
+        m_sharedPreferences.edit().putBoolean(PREF_WIFIDIRECT_STATUS, isChecked).apply();
+
+        if (isChecked) {
+          startNDNOverWifiDirect();
+          startUiUpdateLoop();
+        } else {
+          stopNDNOverWifiDirect();
+          stopUiUpdateLoop();
+          resetUi();
+        }
+      }
+    });
+
+    // list view for displaying peers
+    m_wdConnectedPeerListview = (ListView) view.findViewById(R.id.wd_connected_peers_listview);
+    m_ConnectedPeers = new ArrayList<>(NDNController.getInstance().getConnectedPeers());
+    m_DicoveredPeers = new ArrayList<>(NDNController.getInstance().getDiscoveredPeers());
+
+    m_ConnectedPeersAdapter = new ConnectPeerListAdapter(getActivity(), R.layout.row_devices, m_ConnectedPeers);
+    m_wdConnectedPeerListview.setAdapter(m_ConnectedPeersAdapter);
+    m_wdConnectedPeerListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+      @Override
+      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        Peer selectedPeer = (Peer) parent.getItemAtPosition(position);
+
+        // toast a quick message!
+        if (selectedPeer == null) {
+          Toast.makeText(getActivity(),
+            getResources().getString(R.string.fragment_wifidirect_toast_peer_no_longer_available),
+            Toast.LENGTH_LONG).show();
+        } else {
+          String peerInfo = selectedPeer.getNumProbeTimeouts() == 0 ?
+            getResources().getString(R.string.fragment_wifidirect_toast_connection_works_well) :
+            getResources().getString(R.string.fragment_wifidirect_toast_didnt_get_response) +
+            (selectedPeer.getNumProbeTimeouts() * NDNController.PROBE_DELAY / 1000) +
+              getResources().getString(R.string.fragment_wifidirect_toast_seconds);
+          Toast.makeText(getActivity(), peerInfo, Toast.LENGTH_LONG).show();
+        }
+      }
+    });
+
+    m_wdDiscoveredPeerListview = (ListView) view.findViewById(R.id.wd_discovered_peers_listview);
+    m_DiscoveredPeersAdapter = new DiscoveredPeerListAdapter(getActivity(), R.layout.row_devices, m_DicoveredPeers);
+    m_wdDiscoveredPeerListview.setAdapter(m_DiscoveredPeersAdapter);
+
+    m_wdDiscoveredPeerListview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+      @Override
+      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        final WifiP2pDevice device = (WifiP2pDevice) parent.getItemAtPosition(position);
+
+        if(device.status == WifiP2pDevice.INVITED) {
+          AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+          builder.setMessage(getResources().getString(R.string.fragment_wifidirect_dialog_cancel_invitation) + device.deviceName + getResources().getString(R.string.question_mark))
+            .setCancelable(false)
+            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+              public void onClick(DialogInterface dialog, int id) {
+                m_progressDialog = show(getActivity(), getResources().getString(R.string.fragment_wifidirect_dialog_cancelling),
+                  getResources().getString(R.string.fragment_wifidirect_dialog_cancelling_invitation) + device.deviceName, true, true
+                );
+                m_progressDialog.setCancelable(false);
+
+                NDNController.getInstance().cancelConnect();
+              }
+            })
+            .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+              public void onClick(DialogInterface dialog, int id) {
+                return;
+              }
+            });
+          AlertDialog dialog = builder.create();
+          dialog.show();
+          return;
+        }
+
+        if(device.status == WifiP2pDevice.CONNECTED) {
+          AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+          String alertMessage = getResources().getString(R.string.fragment_wifidirect_dialog_disconnect_from) + device.deviceName + getResources().getString(R.string.question_mark);
+          if(NDNController.getInstance().getIsGroupOwner()) {
+            alertMessage = alertMessage + getResources().getString(R.string.fragment_wifidirect_dialog_destroy_group_alter);
+          }
+          builder.setMessage(alertMessage)
+            .setCancelable(false)
+            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+              public void onClick(DialogInterface dialog, int id) {
+                m_progressDialog = show(getActivity(),  getResources().getString(R.string.fragment_wifidirect_dialog_disconnecting),
+                  getResources().getString(R.string.fragment_wifidirect_dialog_disconnecting_from) + device.deviceName, true, true
+                );
+                m_progressDialog.setCancelable(false);
+
+                NDNController.getInstance().disconnect();
+              }
+            })
+            .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+              public void onClick(DialogInterface dialog, int id) {
+                return;
+              }
+            });
+          AlertDialog dialog = builder.create();
+          dialog.show();
+          return;
+        }
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(getResources().getString(R.string.fragment_wifidirect_dialog_invite) +
+          device.deviceName + getResources().getString(R.string.fragment_wifidirect_dialog_join_group)
+          + getResources().getString(R.string.question_mark))
+          .setCancelable(false)
+          .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+              m_progressDialog = ProgressDialog.show(getActivity(), getResources().getString(R.string.fragment_wifidirect_dialog_inviting),
+                getResources().getString(R.string.fragment_wifidirect_dialog_inviting) + device.deviceName + getResources().getString(R.string.fragment_wifidirect_dialog_join_group), true, true
+              );
+              m_progressDialog.setCancelable(false);
+
+              NDNController.getInstance().connect(device);
+            }
+          })
+          .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+              return;
+            }
+          });
+        AlertDialog dialog = builder.create();
+        dialog.show();
+      }
+    });
+    return view;
+  }
+
+  @Override
+  public void onAttach(Context context) {
+    super.onAttach(context);
+  }
+
+  @Override
+  public void onDetach() {
+    super.onDetach();
+  }
+
+  @Override
+  public void onPause() {
+    super.onPause();
+    stopUiUpdateLoop();
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    startUiUpdateLoop();
+  }
+
+  private void startNDNOverWifiDirect() {
+    // this is set so that NDNController has context to start appropriate services
+    NDNController.getInstance().setWifiDirectContext(getActivity());
+
+    // only start if the device is not currently connected to a group
+    // otherwise, the protocol must have been running to begin with!
+    if (myAddress == null) {
+      // main wrapper function that begins all elements of the protocol
+      NDNController.getInstance().start();
+    }
+  }
+
+  private void stopNDNOverWifiDirect() {
+    // main wrapper function that stops all elements of the protocol
+    NDNController.getInstance().stop();
+  }
+
+  private void startUiUpdateLoop() {
+    // periodically check for changed state
+    // to display to user
+    m_handler.post(m_UiUpdateRunnable);
+  }
+
+  private void stopUiUpdateLoop() {
+    m_handler.removeCallbacks(m_UiUpdateRunnable);
+  }
+
+  private void resetUi() {
+    // simply resets what is displayed to user
+    m_wdIpAddress.setText(getResources().getString(R.string.empty_string));
+    m_wdGroupConnStatus.setText(getResources().getString(R.string.empty_string));
+    m_wdIsGroupOwner.setText(getResources().getString(R.string.empty_string));
+    m_ConnectedPeers.clear();
+    m_DicoveredPeers.clear();
+    m_ConnectedPeersAdapter.notifyDataSetChanged();
+    m_DiscoveredPeersAdapter.notifyDataSetChanged();
+  }
+
+  /**
+   * Array adapter for ListFragment that maintains WifiP2pDevice list.
+   */
+  private class ConnectPeerListAdapter extends ArrayAdapter<Peer> {
+
+    private List<Peer> items;
+
+    /**
+     * @param context
+     * @param textViewResourceId
+     * @param objects
+     */
+    public ConnectPeerListAdapter(Context context, int textViewResourceId,
+                                     List<Peer> objects) {
+      super(context, textViewResourceId, objects);
+      items = objects;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      View v = convertView;
+      if (v == null) {
+        LayoutInflater vi = (LayoutInflater) getActivity().getSystemService(
+          Context.LAYOUT_INFLATER_SERVICE);
+        v = vi.inflate(R.layout.row_devices, null);
+      }
+      Peer peer = items.get(position);
+      if (peer != null) {
+        TextView top = (TextView) v.findViewById(R.id.device_name);
+        TextView bottom = (TextView) v.findViewById(R.id.device_details);
+        if (top != null) {
+          top.setText(peer.getDevice() == null ? getResources().getString(R.string.empty_string) : peer.getDevice().deviceName);
+        }
+        if (bottom != null) {
+          bottom.setText(peer.getIpAddress());
+        }
+      }
+
+      return v;
+    }
+  }
+
+  /**
+   * Array adapter for ListFragment that maintains WifiP2pDevice list.
+   */
+  private class DiscoveredPeerListAdapter extends ArrayAdapter<WifiP2pDevice> {
+
+    private List<WifiP2pDevice> items;
+
+    /**
+     * @param context
+     * @param textViewResourceId
+     * @param objects
+     */
+    public DiscoveredPeerListAdapter(Context context, int textViewResourceId,
+                               List<WifiP2pDevice> objects) {
+      super(context, textViewResourceId, objects);
+      items = objects;
+
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      View v = convertView;
+      if (v == null) {
+        LayoutInflater vi = (LayoutInflater) getActivity().getSystemService(
+          Context.LAYOUT_INFLATER_SERVICE);
+        v = vi.inflate(R.layout.row_devices, null);
+      }
+      WifiP2pDevice device = items.get(position);
+      if (device != null) {
+        TextView top = (TextView) v.findViewById(R.id.device_name);
+        TextView bottom = (TextView) v.findViewById(R.id.device_details);
+        if (top != null) {
+          top.setText(device.deviceName);
+        }
+        if (bottom != null) {
+          bottom.setText(getDeviceStatus(device.status));
+        }
+      }
+
+      return v;
+    }
+  }
+  ////////////////////////////////////////////////////////////////////////////////////
+  private ListView m_wdConnectedPeerListview;
+  private ListView m_wdDiscoveredPeerListview;
+  private Switch m_wdSwitch;
+  private TextView m_wdGroupConnStatus;
+  private TextView m_wdIpAddress;
+  private TextView m_wdDeviceName;
+  private TextView m_wdIsGroupOwner;
+
+  private Handler m_handler;
+  private Runnable m_UiUpdateRunnable = new Runnable() {
+    @Override
+    public void run() {
+      if (m_progressDialog != null && m_progressDialog.isShowing()) {
+        m_progressDialog.dismiss();
+      }
+      if (myAddress != null) {
+        m_wdGroupConnStatus.setText(getResources().getString(R.string.fragment_wifidirect_text_group_connected));
+        m_wdIpAddress.setText(myAddress);
+        if(NDNController.getInstance().getIsGroupOwner()) {
+          m_wdIsGroupOwner.setText(getResources().getString(R.string.yes));
+        } else {
+          m_wdIsGroupOwner.setText(getResources().getString(R.string.no));
+        }
+      } else {
+        if (!m_sharedPreferences.getBoolean(PREF_WIFIDIRECT_STATUS, false)) {
+          m_wdGroupConnStatus.setText("");
+        } else {
+          if(NDNController.getInstance().WIFI_STATE == WifiP2pManager.WIFI_P2P_STATE_ENABLED)
+            m_wdGroupConnStatus.setText(getResources().getString(R.string.fragment_wifidirect_text_group_scanning));
+          else {
+            m_wdGroupConnStatus.setText(getResources().getString(R.string.fragment_wifidirect_text_group_wifi_p2p_disabled));
+          }
+        }
+
+        m_wdIpAddress.setText(getResources().getString(R.string.empty_string));
+        m_wdIsGroupOwner.setText(getResources().getString(R.string.empty_string));
+      }
+      if (myDeviceName != null) {
+        m_wdDeviceName.setText(myDeviceName);
+      } else {
+        m_wdDeviceName.setText(getResources().getString(R.string.empty_string));
+      }
+
+      // refresh the list of peers
+      m_ConnectedPeers.clear();
+      m_DicoveredPeers.clear();
+      m_ConnectedPeers.addAll(NDNController.getInstance().getConnectedPeers());
+      m_DicoveredPeers.addAll(NDNController.getInstance().getDiscoveredPeers());
+      m_ConnectedPeersAdapter.notifyDataSetChanged();
+      m_DiscoveredPeersAdapter.notifyDataSetChanged();
+
+      // call again in X seconds
+      m_handler.postDelayed(m_UiUpdateRunnable, UI_UPDATE_DELAY_MS);
+    }
+  };
+
+  private static String getDeviceStatus(int deviceStatus) {
+    Log.d(TAG, "Peer status :" + deviceStatus);
+    switch (deviceStatus) {
+      case WifiP2pDevice.AVAILABLE:
+        return "Available";
+      case WifiP2pDevice.INVITED:
+        return "Invited";
+      case WifiP2pDevice.CONNECTED:
+        return "Connected";
+      case WifiP2pDevice.FAILED:
+        return "Failed";
+      case WifiP2pDevice.UNAVAILABLE:
+        return "Unavailable";
+      default:
+        return "Unknown";
+    }
+  }
+
+  private SharedPreferences m_sharedPreferences;
+  private List<Peer> m_ConnectedPeers;
+  private List<WifiP2pDevice> m_DicoveredPeers;
+  private ConnectPeerListAdapter m_ConnectedPeersAdapter;
+  private DiscoveredPeerListAdapter m_DiscoveredPeersAdapter;
+  private ProgressDialog m_progressDialog;
+
+  private final int UI_UPDATE_DELAY_MS = 5000;
+
+  private static final String PREF_WIFIDIRECT_STATUS = "WIFIDIRECT_STATUS";
+}
diff --git a/app/src/main/java/net/named_data/nfd/utils/NfdcHelper.java b/app/src/main/java/net/named_data/nfd/utils/NfdcHelper.java
index 178870c..672003e 100644
--- a/app/src/main/java/net/named_data/nfd/utils/NfdcHelper.java
+++ b/app/src/main/java/net/named_data/nfd/utils/NfdcHelper.java
@@ -1,6 +1,6 @@
 /* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2015-2016 Regents of the University of California
+ * Copyright (c) 2015-2017 Regents of the University of California
  * <p>
  * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
  * See AUTHORS.md for complete list of NFD Android authors and contributors.
@@ -26,6 +26,7 @@
 import com.intel.jndn.management.Nfdc;
 import com.intel.jndn.management.enums.FacePersistency;
 import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.FibEntry;
 import com.intel.jndn.management.types.ForwarderStatus;
 import com.intel.jndn.management.types.RibEntry;
 import com.intel.jndn.management.types.Route;
@@ -33,9 +34,8 @@
 import net.named_data.jndn.ControlParameters;
 import net.named_data.jndn.Face;
 import net.named_data.jndn.ForwardingFlags;
-import net.named_data.jndn.Interest;
 import net.named_data.jndn.Name;
-import net.named_data.jndn.security.*;
+import net.named_data.jndn.security.KeyChain;
 import net.named_data.jndn.security.SecurityException;
 import net.named_data.jndn.security.identity.IdentityManager;
 import net.named_data.jndn.security.identity.MemoryIdentityStorage;
@@ -45,7 +45,6 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -112,6 +111,14 @@
   }
 
   /**
+   * Unregisters prefix
+   */
+  public void
+  ribUnregisterPrefix(Name prefix) throws ManagementException {
+    Nfdc.unregister(m_face, prefix);
+  }
+
+  /**
    * List all of routes (RIB entries)
    */
   public List<RibEntry>
@@ -119,6 +126,10 @@
     return Nfdc.getRouteList(m_face);
   }
 
+  public List<FibEntry> fibList() throws ManagementException {
+    return Nfdc.getFibList(m_face);
+  }
+
   public SparseArray<Set<Name>>
   ribAsFaceIdPrefixNameArray() throws ManagementException {
     List<RibEntry> ribEntryList = ribList();
@@ -172,6 +183,17 @@
     return result;
   }
 
+  /**
+   * List all faces
+   * @return
+   * @throws ManagementException
+   */
+  public List<FaceStatus>
+  faceList() throws ManagementException
+  {
+    return Nfdc.getFaceList(m_face);
+  }
+
   public SparseArray<FaceStatus>
   faceListAsSparseArray(Context context) throws ManagementException {
     List<FaceStatus> faceList = faceList(context);
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/callback/GenericCallback.java b/app/src/main/java/net/named_data/nfd/wifidirect/callback/GenericCallback.java
new file mode 100644
index 0000000..547a410
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/callback/GenericCallback.java
@@ -0,0 +1,34 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.callback;
+
+/**
+ * Generic callback interface used for representing
+ * anonymous functions.
+ */
+
+public interface GenericCallback {
+
+  /**
+   * No-arg function that all implementing classes must
+   * define.
+   */
+  public void doJob();
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallBackOnInterest.java b/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallBackOnInterest.java
new file mode 100644
index 0000000..b97e369
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallBackOnInterest.java
@@ -0,0 +1,45 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.callback;
+
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.InterestFilter;
+import net.named_data.jndn.Name;
+
+/**
+ * Interface specification of a general callback
+ * that takes in all input from a regular NDN OnInterest
+ * callback, but fulfills a certain job.
+ */
+public interface NDNCallBackOnInterest {
+
+  /**
+   * Do something particular with available information onInterest.
+   *
+   * @param prefix           NDN Name
+   * @param interest         NDN Interest
+   * @param face             NDN Face
+   * @param interestFilterId the interest filter ID
+   * @param filter           the InterestFilter
+   */
+  public void doJob(Name prefix, Interest interest, Face face,
+                    long interestFilterId, InterestFilter filter);
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallbackOnData.java b/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallbackOnData.java
new file mode 100644
index 0000000..e2c0ac6
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/callback/NDNCallbackOnData.java
@@ -0,0 +1,39 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.callback;
+
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+
+/**
+ * Interface specification of a general callback
+ * that takes in all input from a regular NDN OnData
+ * callback, but fulfills a certain job.
+ */
+public interface NDNCallbackOnData {
+
+  /**
+   * Do something particular with the interest and data packets.
+   *
+   * @param interest NDN Interest
+   * @param data     NDN Data corresponding to the Interest
+   */
+  public void doJob(Interest interest, Data data);
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnData.java b/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnData.java
new file mode 100644
index 0000000..0e815af
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnData.java
@@ -0,0 +1,125 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.callback;
+
+import android.util.Log;
+
+import com.intel.jndn.management.types.FibEntry;
+import com.intel.jndn.management.types.NextHopRecord;
+
+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.nfd.wifidirect.utils.NDNController;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Handle OnData events for outgoing probe interests.
+ */
+public class ProbeOnData implements NDNCallbackOnData {
+
+  private static final String TAG = "ProbeOnData";
+  private NDNController mController = NDNController.getInstance();
+  private Face mFace = mController.getLocalHostFace();
+
+  @Override
+  public void doJob(Interest interest, Data data) {
+    // interest name = /localhop/wifidirect/<toIp>/<fromIp>/probe%timestamp
+    Log.d(TAG, "Got data for interest: " + interest.getName().toString());
+
+    String[] nameArr = interest.getName().toString().split("/");
+    String peerIp = nameArr[nameArr.length - 3];
+    int peerFaceId = mController.getFaceIdForPeer(peerIp);
+
+    // parse the data, update controller prefix map
+    /**
+     * Data is in form:
+     * {numPrefixes}\n
+     * prefix1\n
+     * prefix2\n
+     * ...
+     */
+    String[] responseArr = data.getContent().toString().split("\n");
+
+    // validation
+    if (peerFaceId == -1) {
+      Log.e(TAG, "Undocumented peer.");
+      return;
+    }
+
+    int numPrefixes = Integer.parseInt(responseArr[0]);
+    HashSet<String> prefixesInResp = new HashSet<>(numPrefixes);
+    for (int i = 1; i <= numPrefixes; i++) {
+      prefixesInResp.add(responseArr[i]);
+    }
+
+    // enumerate FIB entries, and collect the set of data prefixes towards this peer
+    HashSet<String> prefixesRegisteredForPeer = new HashSet<>();
+    try {
+      List<FibEntry> fibEntries = mController.getNfdcHelper().fibList();
+      for (FibEntry fibEntry : fibEntries) {
+        //if (fibEntry.getPrefix().toString().startsWith(NDNController.DATA_PREFIX)) {
+        String fibEntryPrefix = fibEntry.getPrefix().toString();
+        if (!fibEntryPrefix.startsWith("/localhop") && !fibEntryPrefix.startsWith("/localhost")) {
+          List<NextHopRecord> nextHopRecords = fibEntry.getNextHopRecords();
+          for (NextHopRecord nextHopRecord : nextHopRecords) {
+            if (nextHopRecord.getFaceId() == peerFaceId) {
+              prefixesRegisteredForPeer.add(fibEntryPrefix);
+            }
+          }
+        }
+      }
+
+      // iterate through prefixes found in response,
+      // removing any already registered prefixes for this peer
+      // any prefix remaining in prefixesRegisteredForPeer after this
+      // is no longer advertised by peer
+      Iterator<String> it = prefixesInResp.iterator();
+      while (it.hasNext()) {
+        String prefix = it.next();
+        if (prefixesRegisteredForPeer.contains(prefix)) {
+          it.remove();
+          prefixesRegisteredForPeer.remove(prefix);
+        }
+      }
+
+      // register new prefixes in response
+      if (prefixesInResp.size() > 0) {
+        Log.d(TAG, prefixesInResp.size() + " new prefixes to add.");
+        mController.ribRegisterPrefix(peerFaceId, prefixesInResp.toArray(new String[0]));
+      } else {
+        Log.d(TAG, "No new prefixes to register.");
+      }
+
+      // unregister all prefixes that no longer are supported via this face
+      for (String toRemovePrefix : prefixesRegisteredForPeer) {
+        Log.d(TAG, "Removing from FIB: " + toRemovePrefix + " " + peerFaceId);
+        mController.getNfdcHelper().ribUnregisterPrefix(new Name(toRemovePrefix), peerFaceId);
+      }
+
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnInterest.java b/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnInterest.java
new file mode 100644
index 0000000..9dd8a41
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/callback/ProbeOnInterest.java
@@ -0,0 +1,138 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.callback;
+
+import android.util.Log;
+
+import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.FibEntry;
+import com.intel.jndn.management.types.NextHopRecord;
+
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.InterestFilter;
+import net.named_data.jndn.MetaInfo;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.util.Blob;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Handle OnInterest events for incoming probe interests.
+ */
+public class ProbeOnInterest implements NDNCallBackOnInterest {
+
+  private static final String TAG = "ProbeOnInterest";
+  private static final int DATA_LIFE_TIME = 500; // this should be smaller than NDNController.PROBE_INTEREST_LIFETIME
+
+  private NDNController mController = NDNController.getInstance();
+
+
+  @Override
+  public void doJob(Name prefix, Interest interest, Face face, long interestFilterId, InterestFilter filter) {
+    Log.d(TAG, "Got an interest for: " + interest.getName().toString());
+
+    // /localhop/wifidirect/192.168.49.x/192.168.49.y/probe?mustBeFresh=1
+    String[] prefixArr = interest.getName().toString().split("/");
+
+    // validate
+    if (prefixArr.length != 6) {
+      Log.e(TAG, "Error with this interest, skipping...");
+    }
+
+    final String peerIp = prefixArr[prefixArr.length - 2];
+
+    // if not logged (a face created for this probing peer), should then create a face (mainly for GO)
+    if (mController.getFaceIdForPeer(peerIp) == -1) {
+
+      mController.createFace(peerIp, NDNController.URI_TRANSPORT_PREFIX, new GenericCallback() {
+        @Override
+        public void doJob() {
+          Log.d(TAG, "Registering localhop for: " + peerIp);
+          String[] prefixes = new String[1];
+          prefixes[0] = NDNController.PROBE_PREFIX + "/" + peerIp;
+          mController.ribRegisterPrefix(mController.getFaceIdForPeer(peerIp),
+            prefixes);
+        }
+      });
+    }
+
+    // enumerate RIB, look for all /ndn/wifidirect/* data prefixes, return to user as described in slides
+    try {
+      // set of prefixes to return to interest sender
+      HashSet<String> prefixesToReturn = new HashSet<>();
+      String response = "";
+      int num = 0;
+
+      // consult NFD to get all entries in FIB
+      List<FibEntry> fibEntries = mController.getNfdcHelper().fibList();
+
+      // enumerate all faces
+      List<FaceStatus> faceStatuses = mController.getNfdcHelper().faceList();
+      HashSet<Integer> faceIds = new HashSet<>();
+      for (FaceStatus faceStatus : faceStatuses) {
+        faceIds.add(faceStatus.getFaceId());
+      }
+
+      // remove the interest incomming face id
+      faceIds.remove(mController.getFaceIdForPeer(peerIp));
+
+      // return only those prefixes that are handled by faces except for the interest incomming face
+      for (FibEntry fibEntry : fibEntries) {
+        if (!fibEntry.getPrefix().toString().startsWith("/localhop") &&
+          !fibEntry.getPrefix().toString().startsWith("/localhost")) {
+          // added constraint that the prefix must be served from devices except for the interest
+          // incomming device (e.g. by an upper layer application)
+          List<NextHopRecord> nextHopRecords = fibEntry.getNextHopRecords();
+          for (NextHopRecord nextHopRecord : nextHopRecords) {
+            if (faceIds.contains(nextHopRecord.getFaceId())) {
+              prefixesToReturn.add(fibEntry.getPrefix().toString());
+              num++;
+              break;
+            }
+          }
+        }
+      }
+
+      Data data = new Data();
+      data.setName(new Name(interest.getName().toUri()));
+      MetaInfo metaInfo = new MetaInfo();
+      metaInfo.setFreshnessPeriod(DATA_LIFE_TIME);
+      data.setMetaInfo(metaInfo);
+
+      // format payload, for now ignore hopcount as it is not clear whether
+      // it is useful
+      for (String pre : prefixesToReturn) {
+        response += ("\n" + pre);
+      }
+
+      Blob payload = new Blob(num + response); // num + ("\nprefix1\nprefix2...")
+      data.setContent(payload);
+
+      face.putData(data);
+      Log.d(TAG, "Send data for: " + interest.getName().toString());
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/model/Peer.java b/app/src/main/java/net/named_data/nfd/wifidirect/model/Peer.java
new file mode 100644
index 0000000..7bd418f
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/model/Peer.java
@@ -0,0 +1,80 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.model;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+/**
+ * Represents a WifiDirect Peer.
+ */
+public class Peer {
+
+  // members
+  private WifiP2pDevice device;
+  private String ipAddress;
+  private int faceId;
+  private int numProbeTimeouts = 0;   // number of timeouts while probing prefixes from this peer
+
+  public Peer() {
+  }
+
+  public WifiP2pDevice getDevice() {
+    return device;
+  }
+
+  public void setDevice(WifiP2pDevice device) {
+    this.device = device;
+  }
+
+  public String getIpAddress() {
+    return ipAddress;
+  }
+
+  public void setIpAddress(String ipAddress) {
+    this.ipAddress = ipAddress;
+  }
+
+  public int getFaceId() {
+    return faceId;
+  }
+
+  public void setFaceId(int faceId) {
+    this.faceId = faceId;
+  }
+
+  public int getNumProbeTimeouts() {
+    return numProbeTimeouts;
+  }
+
+  public void setNumProbeTimeouts(int numProbeTimeouts) {
+    this.numProbeTimeouts = numProbeTimeouts;
+  }
+
+  @Override
+  public String toString() {
+    return "Peer{" +
+      "name=\"" + device.deviceName + "\"" +
+      ", macAddress=\"" + device.deviceAddress + "\"" +
+      ", ipAddress=\"" + ipAddress + "\"" +
+      ", faceId=" + faceId +
+      ", numProbeTimeouts=" + numProbeTimeouts +
+      '}';
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/DiscoverPeersRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/DiscoverPeersRunnable.java
new file mode 100644
index 0000000..190f7c2
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/DiscoverPeersRunnable.java
@@ -0,0 +1,37 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Initiates peer discovery.
+ */
+
+public class DiscoverPeersRunnable implements Runnable {
+  @Override
+  public void run() {
+    try {
+      NDNController.getInstance().discoverPeers();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceAndRouteConsistencyRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceAndRouteConsistencyRunnable.java
new file mode 100644
index 0000000..1f5738e
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceAndRouteConsistencyRunnable.java
@@ -0,0 +1,128 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import com.intel.jndn.management.ManagementException;
+import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.RibEntry;
+
+import net.named_data.nfd.wifidirect.callback.GenericCallback;
+import net.named_data.nfd.wifidirect.model.Peer;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Checks for the consistency between NDNController's view of
+ * the logged peers and the NFD's. Specifically, this is carried
+ * out by comparing views on active Faces.
+ * <p>
+ * This case occurs when the user delete the face manually. May be some other cases.
+ */
+public class FaceAndRouteConsistencyRunnable implements Runnable {
+  private static final String TAG = "FaceAndRouteConsistency";
+
+  @Override
+  public void run() {
+
+    Log.d(TAG, "Running periodic Face and route consistency check...");
+
+    // first, let's retrieve a set of active FaceIds from NFD.
+    // then, let's compare this set with what NDNController has
+    try {
+      List<FaceStatus> faceStatuses = NDNController.getInstance().getNfdcHelper().faceList();
+
+      List<RibEntry> routeStatus = NDNController.getInstance().getNfdcHelper().ribList();
+
+      // put face ids in an easy to access manner
+      HashSet<Integer> nfdActiveFaceIds = new HashSet<>(faceStatuses.size());
+      for (FaceStatus faceStatus : faceStatuses) {
+        nfdActiveFaceIds.add(faceStatus.getFaceId());
+      }
+
+      List<String> peersWithoutFace = new ArrayList<>();
+
+      Map<String, Peer> connectedPeers = NDNController.getInstance().getConnectedPeersMap();
+
+      // create faces if needed
+      for (final String ip : connectedPeers.keySet()) {
+        int peerFaceId = connectedPeers.get(ip).getFaceId();
+        if ((peerFaceId != -1) && (!nfdActiveFaceIds.contains(peerFaceId))) {
+          // create the face but not destroy the logged peers
+          peersWithoutFace.add(ip);
+          Log.d(TAG, "create face for IP " + ip);
+          NDNController.getInstance().createFace(ip, NDNController.URI_TRANSPORT_PREFIX, new GenericCallback() {
+            @Override
+            public void doJob() {
+              Log.d(TAG, "Registering localhop for: " + ip);
+              String[] prefixes = new String[1];
+              prefixes[0] = NDNController.PROBE_PREFIX + "/" + ip;
+              NDNController.getInstance().ribRegisterPrefix(NDNController.getInstance().getFaceIdForPeer(ip),
+                prefixes);
+            }
+          });
+        }
+      }
+
+      //create routes if needed
+      for (final String ip : connectedPeers.keySet()) {
+        if (peersWithoutFace.contains(ip)) {
+          continue;
+        }
+        String prefix = NDNController.PROBE_PREFIX + "/" + ip;
+        boolean exist = false;
+        for (RibEntry oneRoute : routeStatus) {
+          if (prefix.equals(oneRoute.getName().toUri())) {
+            exist = true;
+            break;
+          }
+        }
+        if (!exist) {
+          Log.d(TAG, "create route " + prefix);
+          NDNController.getInstance().ribRegisterPrefix(NDNController.getInstance().getFaceIdForPeer(ip),
+            new String[]{prefix});
+        }
+      }
+
+      //register own prefix if needed
+      if (NDNController.myAddress != null) {
+        String myPrefix = NDNController.PROBE_PREFIX + "/" + NDNController.myAddress;
+        boolean exist = false;
+        for (RibEntry oneRoute : routeStatus) {
+          if (myPrefix.equals(oneRoute.getName().toUri())) {
+            exist = true;
+            break;
+          }
+        }
+        if (!exist) {
+          NDNController.getInstance().registerOwnLocalhop();
+        }
+      }
+
+    } catch (ManagementException me) {
+      Log.e(TAG, "There was an issue retrieving FaceList from NFD");
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceCreateRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceCreateRunnable.java
new file mode 100644
index 0000000..5221c29
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceCreateRunnable.java
@@ -0,0 +1,86 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import com.intel.jndn.management.ManagementException;
+
+import net.named_data.nfd.wifidirect.callback.GenericCallback;
+import net.named_data.nfd.wifidirect.model.Peer;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Convenience class that creates a Face with the forwarder. A callback
+ * is accpeted via the public setCallback(...) method, and will be called
+ * if and only if face creation succeeds.
+ */
+// task to create a network face without using main thread
+public class FaceCreateRunnable implements Runnable {
+
+  private static final String TAG = "FaceCreateRunnable";
+  private String peerIp;
+  private String faceUri;
+  private NDNController mController = NDNController.getInstance();
+  private GenericCallback callback = null;
+
+  public FaceCreateRunnable(String peerIp, String faceUri) {
+    this.peerIp = peerIp;
+    this.faceUri = faceUri;
+  }
+
+  public void setCallback(GenericCallback callback) {
+    this.callback = callback;
+  }
+
+  @Override
+  public void run() {
+    int faceId = -1;
+
+    try {
+      Log.d(TAG, "-------- Inside face create runnable --------");
+
+      faceId = mController.getNfdcHelper().faceCreate(faceUri);
+
+      Log.d(TAG, "Created Face with Face id: " + faceId);
+      if (faceId != -1) {
+
+        // if face creation successful, log the new peer
+        Peer peer = new Peer();
+        peer.setFaceId(faceId);
+        peer.setIpAddress(peerIp);
+        mController.logPeer(peerIp, peer);
+
+        // invoke callback, if any
+        if (callback != null) {
+          callback.doJob();
+        }
+      }
+
+    } catch (ManagementException me) {
+      Log.e(TAG, me.getMessage());
+    } catch (Exception e) {
+      Log.e(TAG, "" + e.getMessage());
+      e.printStackTrace();
+    }
+
+    Log.d(TAG, "---------- END face create runnable -----------");
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceDestroyRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceDestroyRunnable.java
new file mode 100644
index 0000000..add85dd
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceDestroyRunnable.java
@@ -0,0 +1,54 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import com.intel.jndn.management.ManagementException;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Attempts to destroy a given Face, denoted by its Face id.
+ */
+public class FaceDestroyRunnable implements Runnable {
+  private int faceId;
+
+  public FaceDestroyRunnable(int faceId) {
+    this.faceId = faceId;
+  }
+
+  private static final String TAG = "FaceDestroyRunnable";
+
+  @Override
+  public void run() {
+    try {
+      Log.d(TAG, "-------- Inside face destroy task --------");
+      // attempt to destroy Face Id, specified as the first and only parameter
+      NDNController.getInstance().getNfdcHelper().faceDestroy(faceId);
+      Log.d(TAG, "Successfully destroyed Face with Face id: " + faceId);
+    } catch (ManagementException me) {
+      Log.e(TAG, me.getMessage());
+    } catch (Exception e) {
+      Log.e(TAG, e.getMessage());
+    }
+    Log.d(TAG, "---------- END face destroy task -----------");
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceEventProcessRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceEventProcessRunnable.java
new file mode 100644
index 0000000..237bce6
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/FaceEventProcessRunnable.java
@@ -0,0 +1,35 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+public class FaceEventProcessRunnable implements Runnable {
+  private static final String TAG = "FaceEventProcess";
+
+  @Override
+  public void run() {
+    try {
+      NDNController.getInstance().getLocalHostFace().processEvents();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/GroupStatusConsistencyRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/GroupStatusConsistencyRunnable.java
new file mode 100644
index 0000000..c85183c
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/GroupStatusConsistencyRunnable.java
@@ -0,0 +1,56 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * If connected peers list keeps empty, while myaddress keeps un-empty for 1 minute, remove the group
+ */
+
+public class GroupStatusConsistencyRunnable implements Runnable {
+
+  private static final String TAG = "GroupStatusConsistency";
+
+
+  //Notice: (this number) * (running interval) should equal to 1 minute
+  public static final int MAX_TIMEOUTS_ALLOWED = 6;
+  public static int TIME_OUT_TIMES = 0;
+
+  public static void resetTimeoutTimes() {
+    TIME_OUT_TIMES = 0;
+  }
+
+  @Override
+  public void run() {
+    Log.d(TAG, "Check GroupStatusConsistency");
+    if(NDNController.getInstance().isNumOfConnectedPeersZero() && NDNController.myAddress != null) {
+      TIME_OUT_TIMES ++;
+    } else {
+      TIME_OUT_TIMES = 0;
+    }
+    if(TIME_OUT_TIMES >= MAX_TIMEOUTS_ALLOWED) {
+      NDNController.getInstance().disconnect();
+      TIME_OUT_TIMES = 0;
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/ProbeRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/ProbeRunnable.java
new file mode 100644
index 0000000..9741633
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/ProbeRunnable.java
@@ -0,0 +1,133 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.nfd.wifidirect.callback.ProbeOnData;
+import net.named_data.nfd.wifidirect.model.Peer;
+import net.named_data.nfd.wifidirect.utils.IPAddress;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+import java.io.IOException;
+
+import static net.named_data.nfd.wifidirect.utils.NDNController.myAddress;
+
+/**
+ * Probes network for data prefixes, as specified in protocol.
+ */
+public class ProbeRunnable implements Runnable {
+  private static final String TAG = "ProbeRunnable";
+  //Notice: (this number) * (running interval) should equal to 1 minute
+  private static final int MAX_TIMEOUTS_ALLOWED = 60;
+  private NDNController mController = NDNController.getInstance();
+
+  private OnData onData = new OnData() {
+    private ProbeOnData probeOnData = new ProbeOnData();
+
+    @Override
+    public void onData(Interest interest, Data data) {
+      Name interestName = interest.getName();
+      Log.d(TAG, "Got data for interest " + interestName);
+      String peerIp = interestName.get(interestName.size() - 3).toEscapedString();
+      probeOnData.doJob(interest, data);
+      Peer peer = NDNController.getInstance().getPeerByIp(peerIp);
+      if (peer != null)
+        peer.setNumProbeTimeouts(0);    // peer responded, so reset timeout counter
+    }
+  };
+
+  private OnTimeout onTimeout = new OnTimeout() {
+    @Override
+    public void onTimeout(Interest interest) {
+      Name interestName = interest.getName();
+      Log.d(TAG, "interest " + interestName + " times out");
+      String peerIp = interestName.get(interestName.size() - 3).toEscapedString();
+      Peer peer = NDNController.getInstance().getPeerByIp(peerIp);
+      if (peer == null) {
+        Log.d(TAG, "No peer information available to track timeout.");
+        return;
+      }
+
+      Log.d(TAG, "Timeout for interest: " + interest.getName().toString() +
+        " Attempts: " + (peer.getNumProbeTimeouts() + 1));
+
+      if (peer.getNumProbeTimeouts() + 1 >= MAX_TIMEOUTS_ALLOWED) {
+        // This case means, remove a peer which
+        // (1) is indicatated connected by Wifi-Direct
+        // (2) but doesn't response to probeInterest
+        // so remove it (disconnect it and remove saved states)
+        NDNController.getInstance().removePeer(peerIp);
+      } else {
+        peer.setNumProbeTimeouts(peer.getNumProbeTimeouts() + 1);
+      }
+    }
+  };
+
+  @Override
+  public void run() {
+    Log.d(TAG, "start to probe");
+    try {
+      if (IPAddress.getLocalIPAddress() == null) {
+
+        // this means that a disconnect has recently occurred and this device
+        // is no longer a part of a group (WDBroadcastReceiver.myAddress is this
+        // device's previous WD IP)
+        if (myAddress != null) {
+          Log.d(TAG, "A disconnect has been detected, refreshing state...");
+
+          // unregister the previous "/localhop/wifidirect/..." prefix
+          mController.cleanUpConnections();
+          // recreateFace for future connection
+          mController.recreateFace();
+
+          // most likely will have a new IP to register "/localhop/wifidirect/<IP>"
+          // call this so that the next time a group is joined a new local prefix
+          // registration will occur
+          mController.setHasRegisteredOwnLocalhop(false);
+
+          // ensure that peer diiscovery is running, if it had not been before
+          mController.startDiscoveringPeers();
+        } else {
+          Log.d(TAG, "Skip this iteration due to null WD ip.");
+        }
+
+      } else {
+        for(String ip : NDNController.getInstance().getIpsOfConnectedPeers()) {
+          //send interest to this peer
+          Interest interest = new Interest(new Name(NDNController.PROBE_PREFIX + "/" + ip + "/" + myAddress + "/probe"));
+          interest.setMustBeFresh(true);
+          interest.setInterestLifetimeMilliseconds(NDNController.PROBE_INTEREST_LIFETIME);
+          Log.d(TAG, "Sending interest: " + interest.getName().toString());
+          NDNController.getInstance().getLocalHostFace().expressInterest(interest, onData, onTimeout);
+        }
+      }
+    } catch (IOException ioe) {
+      Log.e(TAG, "Something went wrong with sending a probe interest.");
+      ioe.printStackTrace();
+    }
+  }
+
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RegisterPrefixRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RegisterPrefixRunnable.java
new file mode 100644
index 0000000..9053c36
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RegisterPrefixRunnable.java
@@ -0,0 +1,79 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterestCallback;
+import net.named_data.jndn.OnRegisterFailed;
+import net.named_data.jndn.OnRegisterSuccess;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Task that provides the ability to register a prefix to the specified face.
+ * TODO: Given that multiple successive prefix registration calls can fail (NFD timeout),
+ * This task needs to attempt to register prefixes 5 times or until success. Each
+ * attempt is separated (e.g. 500ms) to increase chance of registration.
+ */
+public class RegisterPrefixRunnable implements Runnable {
+  private final String TAG = "RegisterPrefixRunnable";
+  private OnInterestCallback onInterestCallback;
+
+  private String prefixToRegister;
+
+  public RegisterPrefixRunnable(String prefix, OnInterestCallback cb) {
+    this.prefixToRegister = prefix;
+    this.onInterestCallback = cb;
+  }
+
+  @Override
+  public void run() {
+    Log.d(TAG, "try to register local prefix" + prefixToRegister);
+    try {
+      // allow child inherit
+      final ForwardingFlags flags = new ForwardingFlags();
+      flags.setChildInherit(true);
+
+      Name prefix = new Name(prefixToRegister);
+
+      long registerPrefixId = NDNController.getInstance().getLocalHostFace().registerPrefix(prefix, onInterestCallback,
+        new OnRegisterFailed() {
+          @Override
+          public void onRegisterFailed(Name prefix) {
+            Log.d(TAG, "Failed to register prefix: " + prefix.toString());
+          }
+        }, new OnRegisterSuccess() {
+          @Override
+          public void onRegisterSuccess(Name prefix, long registeredPrefixId) {
+            Log.d(TAG, "Prefix registered successfully: " + prefixToRegister);
+
+          }
+        },
+        flags);
+      Log.d(TAG, "registered prefix id is " + registerPrefixId);
+      NDNController.getInstance().setRegisteredPrefixId(registerPrefixId);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibRegisterPrefixRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibRegisterPrefixRunnable.java
new file mode 100644
index 0000000..4d10e33
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibRegisterPrefixRunnable.java
@@ -0,0 +1,67 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Name;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Convenience class used for registering a prefix towards some Face, denoted by
+ * its Face ID. Note that this class differs from RegisterPrefixRunnable, as the latter
+ * deals with registering prefixes to a localhost face, while this class does not make
+ * that assumption.
+ */
+public class RibRegisterPrefixRunnable implements Runnable {
+
+  private final String TAG = "RibRegister";
+
+  private String prefixToRegister;
+  private int faceId;
+  private int cost;
+  private boolean childInherit;
+  private boolean capture;
+
+  public RibRegisterPrefixRunnable(String prefixToRegister, int faceId, int cost,
+                                   boolean childInherit, boolean capture) {
+    this.prefixToRegister = prefixToRegister;
+    this.capture = capture;
+    this.childInherit = childInherit;
+    this.cost = cost;
+    this.faceId = faceId;
+  }
+
+  @Override
+  public void run() {
+    try {
+      ForwardingFlags flags = new ForwardingFlags();
+      flags.setChildInherit(childInherit);
+      flags.setCapture(capture);
+      NDNController.getInstance().getNfdcHelper().ribRegisterPrefix(new Name(prefixToRegister),
+        faceId, cost, childInherit, capture);
+
+      Log.d(TAG, "registered rib prefix: " + prefixToRegister);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibUnregisterPrefixRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibUnregisterPrefixRunnable.java
new file mode 100644
index 0000000..80a0e7f
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/RibUnregisterPrefixRunnable.java
@@ -0,0 +1,53 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.jndn.Name;
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+/**
+ * Convenience class used for registering a prefix towards some Face, denoted by
+ * its Face ID. Note that this class differs from RegisterPrefixRunnable, as the latter
+ * deals with registering prefixes to a localhost face, while this class does not make
+ * that assumption.
+ */
+public class RibUnregisterPrefixRunnable implements Runnable {
+
+  private static final String TAG = "RibUnregisterTask";
+
+  private String prefixToUnregister;
+
+  public RibUnregisterPrefixRunnable(String prefixToUnregister) {
+    this.prefixToUnregister = prefixToUnregister;
+  }
+
+  @Override
+  public void run() {
+    try {
+      NDNController.getInstance().getNfdcHelper().ribUnregisterPrefix(new Name(prefixToUnregister));
+
+      Log.d(TAG, "Unregistered rib prefix: " + prefixToUnregister);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/runnable/UnregisterPrefixRunnable.java b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/UnregisterPrefixRunnable.java
new file mode 100644
index 0000000..b0a7d08
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/runnable/UnregisterPrefixRunnable.java
@@ -0,0 +1,43 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.runnable;
+
+import android.util.Log;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+
+public class UnregisterPrefixRunnable implements Runnable {
+  private static final String TAG = "UnregisterPrefix";
+
+  private long registeredPrefixId;
+
+  public UnregisterPrefixRunnable(long registeredPrefixId) {
+    this.registeredPrefixId = registeredPrefixId;
+  }
+
+  @Override
+  public void run() {
+    if (registeredPrefixId != -1) {
+      NDNController.getInstance().getLocalHostFace().removeRegisteredPrefix(registeredPrefixId);
+      Log.d(TAG, "unregister prefix whose id is " + registeredPrefixId + " on local face");
+      NDNController.getInstance().setRegisteredPrefixId(-1);
+    }
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/service/WDBroadcastReceiverService.java b/app/src/main/java/net/named_data/nfd/wifidirect/service/WDBroadcastReceiverService.java
new file mode 100644
index 0000000..98b3007
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/service/WDBroadcastReceiverService.java
@@ -0,0 +1,96 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.service;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.IBinder;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import net.named_data.nfd.wifidirect.utils.NDNController;
+import net.named_data.nfd.wifidirect.utils.WDBroadcastReceiver;
+
+/**
+ * Service that registers a WDBroadcastReceiver to listen to WiFi Direct
+ * broadcasted intents.
+ */
+
+public class WDBroadcastReceiverService extends Service {
+
+  private final static String TAG = "WDBRService";
+
+  private WDBroadcastReceiver mReceiver = null;
+  private WifiP2pManager mManager;
+  private WifiP2pManager.Channel mChannel;
+  private IntentFilter mIntentFilter;
+
+  @Nullable
+  @Override
+  public IBinder onBind(Intent intent) {
+    return null;
+  }
+
+  @Override
+  public void onCreate() {
+    Log.d(TAG, "initWifiP2p() service");
+    initWifiP2p();
+  }
+
+  @Override
+  public int onStartCommand(Intent intent, int flags, int startId) {
+    Log.d(TAG, "registerReceiver()");
+    registerReceiver(mReceiver, mIntentFilter);
+
+    // If we get killed, after returning from here, restart
+    return START_STICKY;
+  }
+
+  @Override
+  public void onDestroy() {
+    Log.d(TAG, "onDestroy()");
+
+    if (mReceiver != null) {
+      Log.d(TAG, "unregisterReceiver()");
+      unregisterReceiver(mReceiver);
+    }
+
+    super.onDestroy();
+  }
+
+  /* initialize manager and receiver for activity */
+  private void initWifiP2p() {
+    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
+    mChannel = mManager.initialize(this, getMainLooper(), null);
+    mReceiver = new WDBroadcastReceiver(mManager, mChannel);
+
+    mIntentFilter = new IntentFilter();
+    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+
+    NDNController.getInstance().recordWifiP2pResources(mManager, mChannel);
+  }
+}
+
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/utils/IPAddress.java b/app/src/main/java/net/named_data/nfd/wifidirect/utils/IPAddress.java
new file mode 100644
index 0000000..5b20b73
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/utils/IPAddress.java
@@ -0,0 +1,158 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.utils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+/**
+ * Helps retrieve the current WifiP2p interface IP address.
+ * See: http://stackoverflow.com/questions/10053385/how-to-get-each-devices-ip-address-in-wi-fi-direct-scenario
+ */
+public class IPAddress {
+
+  /**
+   * Returns the WiFi Direct (WifiP2p interface) IP address, or null if not available.
+   *
+   * @return String representation of the WD Ip address if it exists, otherwise null.
+   */
+  public static String getLocalIPAddress() {
+    try {
+      byte[] ip = null;
+      for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+        NetworkInterface intf = en.nextElement();
+        for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+          InetAddress inetAddress = enumIpAddr.nextElement();
+          if (!inetAddress.isLoopbackAddress()) {
+            if (inetAddress instanceof Inet4Address) { // fix for Galaxy Nexus. IPv4 is easy to use :-)
+              ip = inetAddress.getAddress();
+              String niceIp = getDottedDecimalIP(ip);
+              if (niceIp.startsWith("192.168.49")) {  // wifid ip's are all in 192.168.49.x range
+                return niceIp;
+              }
+            }
+            //return inetAddress.getHostAddress().toString(); // Galaxy Nexus returns IPv6
+          }
+        }
+      }
+
+      return null;
+    } catch (SocketException ex) {
+      //Log.e("AndroidNetworkAddressFactory", "getLocalIPAddress()", ex);
+    } catch (NullPointerException ex) {
+      ex.printStackTrace();
+      //Log.e("AndroidNetworkAddressFactory", "getLocalIPAddress()", ex);
+    }
+    return null;
+  }
+
+  private static String getDottedDecimalIP(byte[] ipAddr) {
+    //convert to dotted decimal notation:
+    String ipAddrStr = "";
+    for (int i = 0; i < ipAddr.length; i++) {
+      if (i > 0) {
+        ipAddrStr += ".";
+      }
+      ipAddrStr += ipAddr[i] & 0xFF;
+    }
+    return ipAddrStr;
+  }
+
+  /**
+   * Try to extract a hardware MAC address from a given IP address using the
+   * ARP cache (/proc/net/arp).<br>
+   * <br>
+   * We assume that the file has this structure:<br>
+   * <br>
+   * IP address       HW type     Flags       HW address            Mask     Device
+   * 192.168.18.11    0x1         0x2         00:04:20:06:55:1a     *        eth0
+   * 192.168.18.36    0x1         0x2         00:22:43:ab:2a:5b     *        eth0
+   *
+   * @param ip
+   * @return the MAC from the ARP cache
+   */
+  public static String getMacFromArpCache(String ip) {
+    if (ip == null)
+      return null;
+    BufferedReader br = null;
+    try {
+      br = new BufferedReader(new FileReader("/proc/net/arp"));
+      String line;
+      while ((line = br.readLine()) != null) {
+        String[] splitted = line.split(" +");
+        if (splitted != null && splitted.length >= 4 && ip.equals(splitted[0])) {
+          // Basic sanity check
+          String mac = splitted[3];
+          if (mac.matches("..:..:..:..:..:..")) {
+            return mac;
+          } else {
+            return null;
+          }
+        }
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        br.close();
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    return null;
+  }
+
+  public static String getIPFromMac(String MAC) {
+    BufferedReader br = null;
+    try {
+      br = new BufferedReader(new FileReader("/proc/net/arp"));
+      String line;
+      while ((line = br.readLine()) != null) {
+
+        String[] splitted = line.split(" +");
+        if (splitted != null && splitted.length >= 4) {
+          // Basic sanity check
+          String device = splitted[5];
+          if (device.matches(".*p2p-p2p0.*")) {
+            String mac = splitted[3];
+            if (mac.matches(MAC)) {
+              return splitted[0];
+            }
+          }
+        }
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        br.close();
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    return null;
+  }
+}
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/utils/NDNController.java b/app/src/main/java/net/named_data/nfd/wifidirect/utils/NDNController.java
new file mode 100644
index 0000000..09701ff
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/utils/NDNController.java
@@ -0,0 +1,957 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.util.Log;
+
+import com.intel.jndn.management.ManagementException;
+
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.InterestFilter;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterestCallback;
+import net.named_data.jndn.security.KeyChain;
+import net.named_data.jndn.security.SecurityException;
+import net.named_data.jndn.security.identity.IdentityManager;
+import net.named_data.jndn.security.identity.MemoryIdentityStorage;
+import net.named_data.jndn.security.identity.MemoryPrivateKeyStorage;
+import net.named_data.nfd.utils.NfdcHelper;
+import net.named_data.nfd.wifidirect.callback.GenericCallback;
+import net.named_data.nfd.wifidirect.callback.ProbeOnInterest;
+import net.named_data.nfd.wifidirect.model.Peer;
+import net.named_data.nfd.wifidirect.runnable.DiscoverPeersRunnable;
+import net.named_data.nfd.wifidirect.runnable.FaceAndRouteConsistencyRunnable;
+import net.named_data.nfd.wifidirect.runnable.GroupStatusConsistencyRunnable;
+import net.named_data.nfd.wifidirect.runnable.ProbeRunnable;
+import net.named_data.nfd.wifidirect.service.WDBroadcastReceiverService;
+import net.named_data.nfd.wifidirect.runnable.FaceCreateRunnable;
+import net.named_data.nfd.wifidirect.runnable.FaceDestroyRunnable;
+import net.named_data.nfd.wifidirect.runnable.FaceEventProcessRunnable;
+import net.named_data.nfd.wifidirect.runnable.RegisterPrefixRunnable;
+import net.named_data.nfd.wifidirect.runnable.RibRegisterPrefixRunnable;
+import net.named_data.nfd.wifidirect.runnable.RibUnregisterPrefixRunnable;
+import net.named_data.nfd.wifidirect.runnable.UnregisterPrefixRunnable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * New streamlined NDNOverWifiDirect controller. This class acts as the
+ * manager (hence controller) of the protocol, and applications using
+ * this project need only interface with the returned instance via getInstance().
+ */
+
+public class NDNController implements WifiP2pManager.PeerListListener,
+  WifiP2pManager.ConnectionInfoListener, WifiP2pManager.ChannelListener {
+  // volatile variables accessed in multiple threads
+  public static volatile String groupOwnerAddress;
+  public static volatile String myAddress;
+  public static volatile String myDeviceName;
+  public static volatile int WIFI_STATE = WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+
+  public static final String URI_UDP_PREFIX = "udp://";
+  public static final String URI_TCP_PREFIX = "tcp://";
+  public static final String URI_TRANSPORT_PREFIX = URI_UDP_PREFIX;   // transport portion of uri that rest of project should use
+  public static final String PROBE_PREFIX = "/localhop/wifidirect";   // prefix of prefix used in probing
+
+  private static final String TAG = "NDNController";
+  private static final int DISCOVER_PEERS_DELAY = 5000;  // in ms
+  public static final int PROBE_DELAY = 1000;           // in ms
+  public static final int PROBE_INTEREST_LIFETIME = 1000; // in ms (the network delay should not be large)
+  private static final int FACE_AND_ROUTE_CONSISTENCY_CHECK_DELAY = 5000;
+  private static final int GROUP_STATUS_CONSISTENCY_CHECK_DELAY = 10000;
+
+  // Singleton
+  private static NDNController mController = null;
+  private static KeyChain mKeyChain = null;
+
+  // WiFi Direct related resources
+  private WifiP2pManager wifiP2pManager = null;
+  private WifiP2pManager.Channel channel = null;
+  private Context wifiDirectContext = null;       // context in which WiFi direct operations begin (an activity/fragment)
+  private List<WifiP2pDevice> discoverdPeers = new ArrayList<>();
+
+  // Relevant tasks, services, etc.
+  private WDBroadcastReceiverService brService = null;
+  private Future discoverPeersFuture = null;
+  private Future probeFuture = null;
+  private Future faceAndRouteConsistencyFuture = null;
+  private Future groupStatusConsistencyFuture = null;
+  // keep 1 thread to serialize all the tasks
+  private ScheduledThreadPoolExecutor faceEventProcessExecutor = new ScheduledThreadPoolExecutor(1);
+  private ScheduledThreadPoolExecutor localFaceCommandExecutor = new ScheduledThreadPoolExecutor(1);
+  private ScheduledThreadPoolExecutor nfdcFaceCommandExecutor = new ScheduledThreadPoolExecutor(1);
+  private ScheduledThreadPoolExecutor generalExecutor = new ScheduledThreadPoolExecutor(1);
+
+  // Useful flags
+  private boolean hasRegisteredOwnLocalhop = false;
+  private boolean isGroupOwner;    // set in broadcast receiver, used primarily in ProbeOnInterest
+
+  // we have some redundancy here in data, but difficult to avoid given WiFi Direct API
+  private HashMap<String, Peer> ipPeerMapOfConnectedPeers = new HashMap<>();                  // { peerIp : PeerInstance }, contains at least Face id info
+
+  // single shared Face instance at localhost
+  private Face mFace = null;
+  private final NfdcHelper nfdcHelper = new NfdcHelper();
+  long registeredPrefixId = -1;
+
+  private FaceEventProcessRunnable faceEventProcessRunnable = null;
+  private Future faceEventProcessFuture = null;
+
+
+  /**
+   * Private constructor to prevent outside instantiation.
+   */
+  private NDNController() {
+
+    if (mKeyChain == null) {
+      try {
+        // this is an expensive operation, so minimize it's invocation
+        mKeyChain = buildTestKeyChain();
+      } catch (SecurityException e) {
+        Log.e(TAG, "Unable to build the test keychain.");
+      }
+    }
+  }
+
+  /**
+   * Returns a shared instance of NDNController for use across the library.
+   *
+   * @return NDNController instance
+   */
+  public static NDNController getInstance() {
+    if (mController == null) {
+      mController = new NDNController();
+    }
+
+    return mController;
+  }
+
+  public void setRegisteredPrefixId(long registeredPrefixId) {
+    this.registeredPrefixId = registeredPrefixId;
+  }
+
+  public long getRegisteredPrefixId() {
+    return registeredPrefixId;
+  }
+
+  /**
+   * @return
+   */
+  public List<WifiP2pDevice> getDiscoveredPeers() {
+    return discoverdPeers;
+  }
+
+
+  /**
+   * Logs the peer with the corresponding WD IP address. If a previous logging of
+   * the peer exists, this will replace it.
+   *
+   * @param peerIp The peer's WD IP address
+   * @param peer   A Peer instance with at least FaceId set.
+   */
+  public void logPeer(String peerIp, Peer peer) {
+    ipPeerMapOfConnectedPeers.put(peerIp, peer);
+  }
+
+  /**
+   * Returns the Face id associated with the given peer, denoted by IP address.
+   *
+   * @param peerIp The WiFi Direct IP address of the peer
+   * @return the Face id of the peer or -1 if no mapping exists.
+   */
+  public int getFaceIdForPeer(String peerIp) {
+    if (ipPeerMapOfConnectedPeers.containsKey(peerIp)) {
+      return ipPeerMapOfConnectedPeers.get(peerIp).getFaceId();
+    }
+
+    return -1;
+  }
+
+  /**
+   * Returns the logged peer instance (via logPeer()) by its
+   * WiFi Direct IP address.
+   *
+   * @param ip WiFi Direct IP address of peer
+   * @return the peer instance logged earlier by a call to logPeer(), or null
+   * if none.
+   */
+  public Peer getPeerByIp(String ip) {
+    return ipPeerMapOfConnectedPeers.get(ip);
+  }
+
+  /**
+   * Similar to getConnectPeers, except this returns the IP addresses
+   * of the currently logged peers.
+   *
+   * @return A set backed by the underlying map of logged peers. Thus,
+   * be advised that changes to the returned set will be reflected in
+   * the underlying map.
+   */
+  public Set<String> getIpsOfConnectedPeers() {
+    return ipPeerMapOfConnectedPeers.keySet();
+  }
+
+  public Collection<Peer> getConnectedPeers() {
+    return ipPeerMapOfConnectedPeers.values();
+  }
+
+  public boolean isNumOfConnectedPeersZero() {
+    return ipPeerMapOfConnectedPeers.isEmpty();
+  }
+
+  public Map<String, Peer> getConnectedPeersMap() {
+    return ipPeerMapOfConnectedPeers;
+  }
+
+  /**
+   * Removes mapping to the logged peer, and destroys any state of that peer (e.g. any created
+   * faces and registered prefixes).
+   *
+   * @param ip WiFi Direct IP address of peer
+   */
+  public void removePeer(String ip) {
+    // for now, if the current device is not group owner, it only has one connection, so simply disconnect
+    if (!isGroupOwner) {
+      disconnect();
+    }
+    // if the current device is the group owner, we cannot disconnect the group, but simply remove the
+    // group member.
+    else {
+      FaceDestroyRunnable runnable = new FaceDestroyRunnable(ipPeerMapOfConnectedPeers.get(ip).getFaceId());
+      nfdcFaceCommandExecutor.execute(runnable);
+      ipPeerMapOfConnectedPeers.remove(ip);
+    }
+  }
+
+  /**
+   * Returns whether this device is the group owner
+   *
+   * @return true if this device is GO, false otherwise
+   */
+  public boolean getIsGroupOwner() {
+    return isGroupOwner;
+  }
+
+  /**
+   * Sets whether this device is the group owner
+   *
+   * @param b whether the device is the GO.
+   */
+  public void setIsGroupOwner(boolean b) {
+    isGroupOwner = b;
+  }
+
+  /**
+   * Initializes the WifiP2p context, channel and manager, for use with discovering peers.
+   * This must be done before ever calling discoverPeers().
+   *
+   * @param wifiP2pManager the WifiP2pManager
+   * @param channel        the WifiP2p Channel
+   */
+  public void recordWifiP2pResources(WifiP2pManager wifiP2pManager, WifiP2pManager.Channel channel) {
+    this.wifiP2pManager = wifiP2pManager;
+    this.channel = channel;
+  }
+
+  /**
+   * Must be done before we can start the Broadcast Receiver Service -  in the future we can also
+   * move the starting to somewhere within an activity or fragment in order to avoid this.
+   *
+   * @param context A valid Android context
+   */
+  public void setWifiDirectContext(Context context) {
+    this.wifiDirectContext = context;
+  }
+
+  /**
+   * Creates a face to the specified peer (IP), with the
+   * uriPrefix (e.g. tcp://). Optional callback parameter
+   * for adding a callback function to be called after successful
+   * face creation. Passing in null for callback means no callback.
+   *
+   * @param peerIp    the peer's WiFi Direct IP
+   * @param uriPrefix uri prefix
+   * @param callback  An implementation of GenericCallback, or null. Is called AFTER face
+   *                  creation succeeds.
+   */
+  public void createFace(String peerIp, String uriPrefix, GenericCallback callback) {
+
+    if (peerIp.equals(IPAddress.getLocalIPAddress())) {
+      return; //never add yourself as a face
+    }
+
+    // need to create a new face for this peer
+    FaceCreateRunnable runnable = new FaceCreateRunnable(peerIp, uriPrefix + peerIp);
+
+    if (callback != null) {
+      runnable.setCallback(callback);
+    }
+
+    nfdcFaceCommandExecutor.execute(runnable);
+  }
+
+  /**
+   * Registers the array of prefixes with the given Face, denoted by
+   * its face id.
+   *
+   * @param faceId   The Face Id to register the prefixes to.
+   * @param prefixes array of prefixes to register.
+   */
+  public void ribRegisterPrefix(int faceId, String[] prefixes) {
+    Log.d(TAG, "ribRegisterPrefix called with: " + faceId + " and " + prefixes.length + " prefixes");
+
+    HashSet<Integer> faceIds = new HashSet<>(ipPeerMapOfConnectedPeers.size());
+    for (Peer p : ipPeerMapOfConnectedPeers.values()) {
+      faceIds.add(p.getFaceId());
+    }
+
+    if (faceIds.contains(faceId)) {
+      for (String prefix : prefixes) {
+        Log.d(TAG, "ribRegisterPrefix() with prefix: " + prefix);
+        RibRegisterPrefixRunnable runnable = new RibRegisterPrefixRunnable(prefix, faceId,
+          0, true, false);
+        nfdcFaceCommandExecutor.execute(runnable);
+      }
+    }
+  }
+
+  /**
+   * Begins periodically looking for peers, and connecting
+   * to them.
+   */
+  public void startDiscoveringPeers() {
+    if (discoverPeersFuture == null) {
+      Log.d(TAG, "Start discovering peers every " + DISCOVER_PEERS_DELAY + "ms");
+      DiscoverPeersRunnable runnable = new DiscoverPeersRunnable();
+      discoverPeersFuture = generalExecutor.scheduleWithFixedDelay(runnable, 100, DISCOVER_PEERS_DELAY, TimeUnit.MILLISECONDS);
+    } else {
+      Log.d(TAG, "Discovering peers already running!");
+    }
+  }
+
+  /**
+   * Stops periodically discovering peers and connecting to them.
+   */
+  public void stopDiscoveringPeers() {
+    if (discoverPeersFuture != null) {
+      discoverPeersFuture.cancel(true);
+      discoverPeersFuture = null;
+      Log.d(TAG, "Stopped discovering peers.");
+    }
+  }
+
+  /**
+   * Begins probing the network for data prefixes.
+   */
+  public void startProbing() {
+    if (probeFuture == null) {
+      Log.d(TAG, "Start probing for data prefixes every " + PROBE_DELAY + "ms");
+      ProbeRunnable runnable = new ProbeRunnable();
+      probeFuture = localFaceCommandExecutor.scheduleWithFixedDelay(runnable, 200, PROBE_DELAY, TimeUnit.MILLISECONDS);
+    } else {
+      Log.d(TAG, "Probing task already running!");
+    }
+  }
+
+  /**
+   * Stops probing the network for data prefixes.
+   */
+  public void stopProbing() {
+    if (probeFuture != null) {
+      probeFuture.cancel(true);
+      probeFuture = null;
+      Log.d(TAG, "Stopped probing.");
+    } else {
+      Log.d(TAG, "Pprobing already stopped");
+    }
+  }
+
+  /**
+   * Starts service that registers the broadcast receiver for handling peer discovery
+   */
+  public void startBroadcastReceiverService() {
+    if (brService == null) {
+      Log.d(TAG, "Starting WDBR service...");
+      brService = new WDBroadcastReceiverService();
+      Intent intent = new Intent(wifiDirectContext, WDBroadcastReceiverService.class);
+      wifiDirectContext.startService(intent);
+    } else {
+      Log.d(TAG, "BroadcastReceiverService already started.");
+    }
+  }
+
+  /**
+   * Stops the service that registers the broadcast recevier for handling peer discovery.
+   */
+  public void stopBroadcastReceiverService() {
+    if (brService == null) {
+      Log.d(TAG, "BroadcastReceiverService not running, no need to stop.");
+    } else {
+      if (wifiDirectContext != null) {
+        wifiDirectContext.stopService(new Intent(wifiDirectContext, WDBroadcastReceiverService.class));
+      }
+
+      brService = null;
+      Log.d(TAG, "Stopped WDBR service.");
+    }
+  }
+
+  /**
+   * Starts periodically checking for consistency between NFD and NDNController's view of
+   * active faces.
+   */
+  public void startFaceAndRouteConsistencyChecker() {
+    if (faceAndRouteConsistencyFuture == null) {
+      Log.d(TAG, "Start checking consistency of logged Faces every " +
+        FACE_AND_ROUTE_CONSISTENCY_CHECK_DELAY + "ms");
+      FaceAndRouteConsistencyRunnable runnable = new FaceAndRouteConsistencyRunnable();
+      faceAndRouteConsistencyFuture = nfdcFaceCommandExecutor.scheduleWithFixedDelay(runnable,
+        300, FACE_AND_ROUTE_CONSISTENCY_CHECK_DELAY, TimeUnit.MILLISECONDS);
+    } else {
+      Log.d(TAG, "Face consistency checker already running!");
+    }
+  }
+
+  /**
+   * Stops periodically checking for consistency between NFD and NDNController's view
+   * of active faces.
+   */
+  public void stopFaceAndRouteConsistencyChecker() {
+    if (faceAndRouteConsistencyFuture != null) {
+      faceAndRouteConsistencyFuture.cancel(false);    // do not interrupt if running, but cancel further execution
+      faceAndRouteConsistencyFuture = null;
+
+      Log.d(TAG, "Stopped checking for Face consistency.");
+    } else {
+      Log.d(TAG, "Face consistency checker is already stopped");
+    }
+  }
+
+  /**
+   * Starts periodically checking for consistency between whether there are connected peers and this
+   * device's myAddress.
+   */
+  public void startGroupConsistencyChecker() {
+    if (groupStatusConsistencyFuture == null) {
+      Log.d(TAG, "Start checking consistency of group status every " +
+        GROUP_STATUS_CONSISTENCY_CHECK_DELAY + "ms");
+      GroupStatusConsistencyRunnable.resetTimeoutTimes();
+      GroupStatusConsistencyRunnable runnable = new GroupStatusConsistencyRunnable();
+      groupStatusConsistencyFuture = generalExecutor.scheduleWithFixedDelay(runnable,
+        300, GROUP_STATUS_CONSISTENCY_CHECK_DELAY, TimeUnit.MILLISECONDS);
+    } else {
+      Log.d(TAG, "Group status consistency checker already running!");
+    }
+  }
+
+  /**
+   * Stops periodically checking for consistency between NFD and NDNController's view
+   * of active faces.
+   */
+  public void stopGroupConsistencyChecker() {
+    if (groupStatusConsistencyFuture != null) {
+      groupStatusConsistencyFuture.cancel(false);    // do not interrupt if running, but cancel further execution
+      groupStatusConsistencyFuture = null;
+      GroupStatusConsistencyRunnable.resetTimeoutTimes();
+      Log.d(TAG, "Stopped checking for Face consistency.");
+    } else {
+      Log.d(TAG, "Group status consistency checker is already stopped");
+    }
+  }
+
+  /**
+   * Main convenience wrapper method to start all background
+   * tasks/services for this protocol.
+   */
+  public void start() {
+    recreateFace();
+    startDiscoveringPeers();
+    startProbing();
+    startBroadcastReceiverService();
+    startFaceAndRouteConsistencyChecker();
+    startGroupConsistencyChecker();
+  }
+
+  /**
+   * Main convenience wrapper method to stop all background
+   * tasks/services for this protocol.
+   */
+  public void stop() {
+    cleanUp();
+    stopGroupConsistencyChecker();
+    stopFaceAndRouteConsistencyChecker();
+    stopBroadcastReceiverService();
+    stopProbing();
+    stopDiscoveringPeers();
+  }
+
+  /**
+   * Whether or not /localhop/wifidirect/xxx.xxx.xxx.xxx has
+   * been registered. Here, the ip is specifically that of this device.
+   *
+   * @return true if so, false otherwise
+   */
+  public boolean getHasRegisteredOwnLocalhop() {
+    return this.hasRegisteredOwnLocalhop;
+  }
+
+  /**
+   * Sets the flag for whether the /localhop/wifidiret/xxx.xxx.xxx.xxx prefix is registered.
+   *
+   * @param set true or false
+   */
+  public void setHasRegisteredOwnLocalhop(boolean set) {
+    this.hasRegisteredOwnLocalhop = set;
+  }
+
+  /**
+   * Convenience function to register the important /localhop prefix, necessary for
+   * probe communication.
+   */
+  public void registerOwnLocalhop() {
+    if (!hasRegisteredOwnLocalhop) {
+      Log.d(TAG, "registerOwnLocalhop() starts to work");
+      RegisterPrefixRunnable runnable = new RegisterPrefixRunnable(
+        PROBE_PREFIX + "/" + myAddress, new OnInterestCallback() {
+        @Override
+        public void onInterest(Name prefix, Interest interest, Face face, long interestFilterId, InterestFilter filter) {
+          (new ProbeOnInterest()).doJob(prefix, interest, face, interestFilterId, filter);
+        }
+      });
+      localFaceCommandExecutor.execute(runnable);
+    }
+
+    setHasRegisteredOwnLocalhop(true);
+  }
+
+  /**
+   * Convenience function to unregister the important /localhop prefix, if it was registered
+   * previously.
+   */
+  public void unregisterOwnLocalhop() {
+    if (myAddress != null) {
+      RibUnregisterPrefixRunnable runnable = new RibUnregisterPrefixRunnable(PROBE_PREFIX + "/" + myAddress);
+      nfdcFaceCommandExecutor.execute(runnable);
+    }
+
+    // unregister the prefix, no longer handled if logic gets to here
+    UnregisterPrefixRunnable runnable1 = new UnregisterPrefixRunnable(registeredPrefixId);
+    localFaceCommandExecutor.execute(runnable1);
+    NDNController.getInstance().setHasRegisteredOwnLocalhop(false);
+  }
+
+  /**
+   * Attempts to scan nearby network for WD peers. This can be a one-off
+   * operation, or can be called periodically over time.
+   *
+   * @throws Exception
+   */
+  public void discoverPeers() throws Exception {
+
+    if (wifiP2pManager == null || channel == null) {
+      Log.e(TAG, "Unable to discover peers, did you recordWifiP2pResources() yet?");
+      return;
+    }
+
+    wifiP2pManager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
+      @Override
+      public void onSuccess() {
+        // WIFI_P2P_PEERS_CHANGED_ACTION intent sent!!
+        Log.d(TAG, "Success on discovering peers");
+      }
+
+      @Override
+      public void onFailure(int reasonCode) {
+        String reasonString = WDBroadcastReceiver
+          .getWifiP2pManagerMessageFromReasonCode(reasonCode);
+        Log.d(TAG, "Fail discover peers, reason: " + reasonString);
+      }
+    });
+  }
+
+  /**
+   * Returns a face to localhost, to avoid multiple creations of localhost
+   * faces.
+   *
+   * @return the localhost Face instance.
+   */
+  public Face getLocalHostFace() {
+    return mFace;
+  }
+
+  public NfdcHelper getNfdcHelper() {
+    return nfdcHelper;
+  }
+
+  /**
+   * Resets all state. (including disconnecting group, and reseting saved states)
+   */
+  public void cleanUp() {
+    cancelConnect();
+    disconnect();
+    cleanUpConnections();
+  }
+
+  /**
+   * cancel ongoing negotiation
+   */
+  public void cancelConnect() {
+    // if you are negotiating with others, cancel the connection
+    if (wifiP2pManager != null) {
+      wifiP2pManager.cancelConnect(channel, new WifiP2pManager.ActionListener() {
+        @Override
+        public void onSuccess() {
+          Log.d(TAG, "Successfully removed self from WifiP2p group.");
+        }
+
+        @Override
+        public void onFailure(int reason) {
+          String reasonString = WDBroadcastReceiver
+            .getWifiP2pManagerMessageFromReasonCode(reason);
+          Log.e(TAG, "Unable to remove self from WifiP2p group, reason: " + reasonString);
+        }
+      });
+    }
+  }
+
+  /**
+   * disconnect from a group
+   */
+  public void disconnect() {
+    // if you are in a group, remove yourself from it
+    // note that if you are the group owner, this will cause a disruption in connectivity
+    // for the other peers
+    if (wifiP2pManager != null) {
+      wifiP2pManager.removeGroup(channel, new WifiP2pManager.ActionListener() {
+        @Override
+        public void onSuccess() {
+          Log.d(TAG, "Successfully removed self from WifiP2p group.");
+        }
+
+        @Override
+        public void onFailure(int reason) {
+          String reasonString = WDBroadcastReceiver
+            .getWifiP2pManagerMessageFromReasonCode(reason);
+          Log.e(TAG, "Unable to remove self from WifiP2p group, reason: " + reasonString);
+        }
+      });
+    }
+  }
+
+  /**
+   * start all runnable, keep saved Wifi-Direct states unchanged
+   */
+  public void startRunnables() {
+    startDiscoveringPeers();
+    startProbing();
+    startFaceAndRouteConsistencyChecker();
+    startGroupConsistencyChecker();
+  }
+
+  /**
+   * stop all runnable, keep saved Wifi-Direct states unchanged
+   */
+  public void stopRunnables() {
+    startGroupConsistencyChecker();
+    stopFaceAndRouteConsistencyChecker();
+    stopProbing();
+    stopDiscoveringPeers();
+  }
+
+  /**
+   * Resets all saved states.
+   */
+  public void cleanUpConnections() {
+    unregisterOwnLocalhop();
+
+    // Remove all faces created to peers, and shut down the localhost face we used
+    // for communication with NFD.
+    // Make use of our handy executor
+    Runnable cleanUpRunnable = new Runnable() {
+      @Override
+      public void run() {
+        Log.d(TAG, "before cleaning up connected peers, the size of ipPeerMapOfConnectedPeers is " + ipPeerMapOfConnectedPeers.size());
+
+        for (String peerIp : ipPeerMapOfConnectedPeers.keySet()) {
+          try {
+            Log.d(TAG, "Cleaning up face towards peer: " + peerIp);
+            nfdcHelper.faceDestroy(ipPeerMapOfConnectedPeers.get(peerIp).getFaceId());
+          } catch (ManagementException me) {
+            Log.e(TAG, "Unable to destroy face to: " + peerIp);
+          } catch (Exception e) {
+            Log.e(TAG, "Unable to destroy face to: " + peerIp);
+          }
+        }
+
+        myAddress = null;
+        groupOwnerAddress = null;
+        hasRegisteredOwnLocalhop = false;
+        isGroupOwner = false;
+        ipPeerMapOfConnectedPeers.clear();
+        discoverdPeers.clear();
+      }
+    };
+
+    nfdcFaceCommandExecutor.execute(cleanUpRunnable);
+
+    if (faceEventProcessFuture != null) {
+      faceEventProcessFuture.cancel(false);
+      faceEventProcessFuture = null;
+    }
+    if (mFace != null) {
+      mFace.shutdown();
+      mFace = null;
+    }
+  }
+
+  public void recreateFace() {
+    if (faceEventProcessFuture != null) {
+      faceEventProcessFuture.cancel(false);
+      faceEventProcessFuture = null;
+    }
+    if (mFace != null) {
+      mFace.shutdown();
+      mFace = null;
+    }
+    mFace = new Face();
+    faceEventProcessRunnable = new FaceEventProcessRunnable();
+    try {
+      mFace.setCommandSigningInfo(mKeyChain, mKeyChain.getDefaultCertificateName());
+    } catch (SecurityException e) {
+      Log.e(TAG, "Unable to set command signing info for localhost face.");
+    }
+    faceEventProcessFuture = faceEventProcessExecutor.scheduleWithFixedDelay(
+      faceEventProcessRunnable, 0, 5, TimeUnit.MILLISECONDS);
+    Log.d(TAG, "create face and start to process event");
+  }
+
+  /**
+   * misc
+   **/
+  private KeyChain buildTestKeyChain() throws SecurityException {
+    MemoryIdentityStorage identityStorage = new MemoryIdentityStorage();
+    MemoryPrivateKeyStorage privateKeyStorage = new MemoryPrivateKeyStorage();
+    IdentityManager identityManager = new IdentityManager(identityStorage, privateKeyStorage);
+    KeyChain keyChain = new KeyChain(identityManager);
+    try {
+      keyChain.getDefaultCertificateName();
+    } catch (SecurityException e) {
+      keyChain.createIdentity(new Name("/test/identity"));
+      keyChain.getIdentityManager().setDefaultIdentity(new Name("/test/identity"));
+    }
+    return keyChain;
+
+  }
+
+  /* In the future, we should allow users to implement this method so they can provide their own keychain */
+  public KeyChain getKeyChain() throws SecurityException {
+    return buildTestKeyChain();
+  }
+
+  public void requestConnectionInfo() {
+    wifiP2pManager.requestConnectionInfo(channel, this);
+  }
+
+  /**
+   * update the connected peers info
+   */
+  private void updateIpPeerMapOfConnectedPeers() {
+    Log.d(TAG, "update IpPeerMapOfConnectedPeers");
+    for (String peerIp : ipPeerMapOfConnectedPeers.keySet()) {
+      Peer peer = ipPeerMapOfConnectedPeers.get(peerIp);
+      String macAddress = IPAddress.getMacFromArpCache(peerIp);
+      if (macAddress == null) {
+        continue;
+      }
+      for (WifiP2pDevice one : discoverdPeers) {
+        // TODO: figure out why the 13th char in arp table is 8 smaller than that in device info
+        if (one.deviceAddress.startsWith(macAddress.substring(0, 12))
+          && one.deviceAddress.endsWith(macAddress.substring(13))) {
+          peer.setDevice(one);
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove the unconnected peers (those peers are not removed by the user, but disconnected for
+   * some other reasons, e.g., shut down or out of range) from connected peers map.
+   */
+  private void removeDisconnectedPeersFromIpPeerMapOfConnectedPeers() {
+    Log.d(TAG, "remove unconnected peers from IpPeerMapOfConnectedPeers");
+    Log.d(TAG, "before removing, the size of IpPeerMapOfConnectedPeers is " + ipPeerMapOfConnectedPeers.size());
+    for (String peerIp : ipPeerMapOfConnectedPeers.keySet()) {
+      WifiP2pDevice peer = ipPeerMapOfConnectedPeers.get(peerIp).getDevice();
+      if (peer != null && (!discoverdPeers.contains(peer) || peer.status != WifiP2pDevice.CONNECTED)) {
+        FaceDestroyRunnable runnable = new FaceDestroyRunnable(ipPeerMapOfConnectedPeers.get(peerIp).getFaceId());
+        nfdcFaceCommandExecutor.execute(runnable);
+        ipPeerMapOfConnectedPeers.remove(peerIp);
+      }
+    }
+    Log.d(TAG, "after removing, the size of IpPeerMapOfConnectedPeers is " + ipPeerMapOfConnectedPeers.size());
+  }
+
+  /**
+   * Check Wifi-Direct state, change saved states when needed.
+   * (1) When Wifi-Direct is connected, but myAddress is null, request connection info and save states
+   * (2) When Wifi-Direct is disconnected, but myAddress is not null, clean all the states
+   */
+  private void checkConnectionConsistency() {
+    Log.d(TAG, "start to check ConnectionConsistency");
+    boolean hasConnection = false;
+    for (WifiP2pDevice one : discoverdPeers) {
+      if (one.status == WifiP2pDevice.CONNECTED) {
+        hasConnection = true;
+        break;
+      }
+    }
+    if (hasConnection && myAddress == null) {
+      requestConnectionInfo();
+      return;
+    }
+    if (!hasConnection && myAddress != null) {
+      cleanUpConnections();
+      recreateFace();
+      return;
+    }
+  }
+
+  @Override
+  public void onPeersAvailable(WifiP2pDeviceList peerList) {
+    Log.d(TAG,
+      String.format("Peers available: %d", peerList.getDeviceList().size()));
+
+    discoverdPeers.clear();
+    discoverdPeers.addAll(peerList.getDeviceList());
+
+    updateIpPeerMapOfConnectedPeers();
+    removeDisconnectedPeersFromIpPeerMapOfConnectedPeers();
+    checkConnectionConsistency();
+
+    // If an AdapterView is backed by this data, notify it
+    // of the change.  For instance, if you have a ListView of available
+    // peers, trigger an update.
+    if (discoverdPeers.size() == 0) {
+      Log.d(TAG, "No devices found");
+      return;
+    }
+  }
+
+  @Override
+  public void onConnectionInfoAvailable(WifiP2pInfo info) {
+    Log.d(TAG, "connection info is available!!");
+
+    // check if group formation was successful
+    if (info.groupFormed) {
+
+      // group owner address
+      groupOwnerAddress = info.groupOwnerAddress.getHostAddress();
+
+      // this device's address, which is now available
+      myAddress = IPAddress.getLocalIPAddress();
+      Log.d(TAG, "My WiFi Direct IP address is: " + myAddress);
+
+      if (!getHasRegisteredOwnLocalhop()) {
+        // do so now
+        registerOwnLocalhop();
+        Log.d(TAG, "registerOwnLocalhop() called...");
+      } else {
+        Log.d(TAG, "already registered own /localhop prefix.");
+      }
+
+      if (info.isGroupOwner) {
+        // Do whatever tasks are specific to the group owner.
+        // One common case is creating a server thread and accepting
+        // incoming connections.
+        Log.d(TAG, "I am the group owner, wait for probe interests from peers...");
+        setIsGroupOwner(true);
+      } else {
+        // non group owner
+        // The other device acts as the client. In this case,
+        // you'll want to create a client thread that connects to the group
+        // owner.
+        Log.d(TAG, "I am NOT the group owner.");
+        setIsGroupOwner(false);
+
+        // create a callback that will register the /localhop/wifidirect/<go-addr> prefix
+        GenericCallback cb = new GenericCallback() {
+          @Override
+          public void doJob() {
+            Log.d(TAG, "registering " + NDNController.PROBE_PREFIX + "/" + groupOwnerAddress);
+            String[] prefixes = new String[1];
+            prefixes[0] = NDNController.PROBE_PREFIX + "/" + groupOwnerAddress;
+            ribRegisterPrefix(getFaceIdForPeer(groupOwnerAddress),
+              prefixes);
+          }
+        };
+
+        // create face towards GO, with callback to register /localhop/... prefix
+        createFace(groupOwnerAddress, NDNController.URI_TRANSPORT_PREFIX, cb);
+      }
+    }
+
+  }
+
+  @Override
+  public void onChannelDisconnected() {
+    cleanUpConnections();
+    recreateFace();
+  }
+
+  public void connect(WifiP2pDevice peerDevice) {
+    WifiP2pConfig config = new WifiP2pConfig();
+    config.deviceAddress = peerDevice.deviceAddress;
+    config.wps.setup = WpsInfo.PBC;
+    wifiP2pManager.connect(channel, config, new WifiP2pManager.ActionListener() {
+      @Override
+      public void onSuccess() {
+        // onReceive() in WDBroadcastReceiver will receive an intent
+      }
+
+      @Override
+      public void onFailure(int reason) {
+
+        String reasonString = WDBroadcastReceiver
+          .getWifiP2pManagerMessageFromReasonCode(reason);
+
+        Log.e(TAG, "There was an issue with initiating connection reason: " + reasonString);
+      }
+    });
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/named_data/nfd/wifidirect/utils/WDBroadcastReceiver.java b/app/src/main/java/net/named_data/nfd/wifidirect/utils/WDBroadcastReceiver.java
new file mode 100644
index 0000000..b6b5fa7
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/wifidirect/utils/WDBroadcastReceiver.java
@@ -0,0 +1,161 @@
+/* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015-2017 Regents of the University of California
+ * <p>
+ * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
+ * See AUTHORS.md for complete list of NFD Android authors and contributors.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.wifidirect.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkInfo;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.util.Log;
+
+import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_DISABLED;
+import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+
+/**
+ * WiFi Direct Broadcast receiver. Does not deviate too much
+ * from the standard WiFi Direct broadcast receiver seen in the official
+ * android docs.
+ */
+
+public class WDBroadcastReceiver extends BroadcastReceiver {
+
+  private static final String TAG = "WDBroadcastReceiver";
+
+  // WifiP2p
+  private WifiP2pManager mManager;
+  private WifiP2pManager.Channel mChannel;
+
+  // the controller
+  private NDNController mController;
+
+  public WDBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel) {
+    super();
+
+    this.mManager = manager;
+    this.mChannel = channel;
+    this.mController = NDNController.getInstance();
+  }
+
+  @Override
+  public void onReceive(Context context, Intent intent) {
+    final String action = intent.getAction();
+
+    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
+      // Check to see if Wi-Fi is enabled and notify appropriate activity
+
+      Log.d(TAG, "wifi enabled check");
+      int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
+      if (state == WIFI_P2P_STATE_ENABLED) {
+        // Wifi P2P is enabled
+        Log.d(TAG, "WIFI IS ENABLED");
+        mController.WIFI_STATE = WIFI_P2P_STATE_ENABLED;
+        mController.startRunnables();
+      } else {
+        // Wi-Fi P2P is not enabled
+        Log.d(TAG, "WIFI IS NOT ENABLED");
+        mController.WIFI_STATE = WIFI_P2P_STATE_DISABLED;
+        mController.stopRunnables();
+        mController.cleanUpConnections();
+        mController.recreateFace();
+      }
+
+    } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
+      // Call WifiP2pManager.requestPeers() to get a list of current peers
+
+      Log.d(TAG, "peers changed!");
+
+      // request available peers from the wifi p2p manager. This is an
+      // asynchronous call and the calling activity is notified with a
+      // callback on PeerListListener.onPeersAvailable()
+      if (mManager != null) {
+        mManager.requestPeers(mChannel, mController);
+      }
+
+    } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
+      // Respond to new connection or disconnections
+
+      Log.d(TAG, "p2pconnection changed check");
+      if (mManager == null) {
+        Log.d(TAG, "mManager is null, skipping...");
+        return;
+      }
+
+      NetworkInfo networkInfo = (NetworkInfo) intent
+        .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
+
+      if (networkInfo.isConnected()) {
+        // We are connected with the other device, request connection
+        // info to find group owner IP
+        mManager.requestConnectionInfo(mChannel, mController);
+      } else {
+        // Sometimes the framework sends a disconnect event when connecting to a new peer
+        Log.d(TAG, "Received a disconnect notification!");
+        mController.cleanUpConnections();
+        mController.recreateFace();
+      }
+
+    } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
+      // TODO: Respond to this device's state changing
+      Log.d(TAG, "wifi state changed check");
+      WifiP2pDevice device = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
+      NDNController.myDeviceName = device.deviceName;
+    } else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
+      // if discovery (scanning) has either stopped or resumed
+      switch (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)) {
+        case WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED:
+          Log.d(TAG, "Wifip2p discovery started.");
+          break;
+        case WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED:
+          Log.d(TAG, "Wifipsp discovery stopped.");
+          break;
+        default:
+          Log.d(TAG, "WIFI_P2P_DISCOVERY_CHANGED_ACTION returned other reason.");
+      }
+    }
+  }
+
+  /**
+   * Given an integer reason code returned by a WifiP2pManager.ActionListener,
+   * returns a human-readable message detailing the cause of the error.
+   *
+   * @param reasonCode integer reason code
+   * @return a human-readable message
+   */
+  public static String getWifiP2pManagerMessageFromReasonCode(int reasonCode) {
+    String reasonString;
+    switch (reasonCode) {
+      case WifiP2pManager.BUSY:
+        reasonString = "Framework is busy.";
+        break;
+      case WifiP2pManager.ERROR:
+        reasonString = "There was an error with the request.";
+        break;
+      case WifiP2pManager.P2P_UNSUPPORTED:
+        reasonString = "P2P is unsupported on this device.";
+        break;
+      default:
+        reasonString = "Unknown reason.";
+    }
+
+    return reasonString;
+  }
+}
diff --git a/app/src/main/res/drawable-hdpi/machine.png b/app/src/main/res/drawable-hdpi/machine.png
new file mode 100755
index 0000000..d61609a
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/machine.png
Binary files differ
diff --git a/app/src/main/res/layout/fragment_wifidirect.xml b/app/src/main/res/layout/fragment_wifidirect.xml
new file mode 100644
index 0000000..cb3136b
--- /dev/null
+++ b/app/src/main/res/layout/fragment_wifidirect.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:layout_margin="20dp"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    <ScrollView
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            tools:context="net.named_data.nfd.WiFiDirectFragment">
+
+            <!-- Row with title and on/off switch -->
+            <RelativeLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="10dp">
+                <TextView
+                    android:text="@string/fragment_wifidirect_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <Switch
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/wd_switch"
+                    android:layout_alignParentRight="true" />
+            </RelativeLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:weightSum="1">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/fragment_wifidirect_label_me"
+                    android:layout_weight="0.10" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/wd_this_device_name_textview" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="fill_parent"
+                android:layout_height="1dp"
+                android:gravity="center_vertical"
+                android:background="@color/android_color_gray" />
+
+            <!-- Row for Group connection status -->
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:weightSum="1">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/fragment_wifidirect_group_conn_status"
+                    android:layout_weight="0.10" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/wd_group_conn_status_textview" />
+            </LinearLayout>
+
+            <!-- Row for IP address display -->
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:weightSum="1">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/fragment_wifidirect_curr_ip"
+                    android:layout_weight="0.10"/>
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/wd_ip_address_textview" />
+            </LinearLayout>
+
+            <!-- Row for group owner indicator -->
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:weightSum="1">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/fragment_wifidirect_is_group_own"
+                    android:layout_weight="0.10"/>
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/wd_group_owner_textview" />
+            </LinearLayout>
+        </LinearLayout>
+    </ScrollView>
+
+    <!-- Column of currently connected peers -->
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/fragment_wifidirect_connected_peers"
+        android:layout_marginTop="10dp"/>
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dp"
+        android:gravity="center_vertical"
+        android:background="@color/android_color_gray" />
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/wd_connected_peers_listview"/>
+
+    <!-- Column of discovered peers -->
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/fragment_wifidirect_discovered_peers"
+        android:layout_marginTop="10dp"/>
+    <View
+        android:layout_width="fill_parent"
+        android:layout_height="1dp"
+        android:gravity="center_vertical"
+        android:background="@color/android_color_gray" />
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/wd_discovered_peers_listview"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/row_devices.xml b/app/src/main/res/layout/row_devices.xml
new file mode 100755
index 0000000..baf49e3
--- /dev/null
+++ b/app/src/main/res/layout/row_devices.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:padding="6dip">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_marginRight="2dip"
+        android:src="@drawable/machine" />
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="fill_parent">
+        <TextView
+            android:id="@+id/device_name"
+            android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:gravity="center_vertical" />
+        <TextView
+            android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:id="@+id/device_details"
+            android:ellipsize="marquee" />
+    </LinearLayout>
+</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 ff141d1..5aa76b3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,6 +2,10 @@
     <string name="start">Start</string>
     <string name="stop">Stop</string>
     <string name="app_name">NFD</string>
+    <string name="yes">Yes</string>
+    <string name="no">No</string>
+    <string name="empty_string">""</string>
+    <string name="question_mark">\?</string>
     <string name="service_name">NFD Service</string>
     <string name="action_settings">Settings</string>
     <string name="nfd_stopped">NFD is stopped</string>
@@ -20,6 +24,32 @@
     <string name="drawer_item_ping">Ping</string>
     <string name="drawer_item_strategies">Strategies</string>
     <string name="drawer_item_logcat">Logcat</string>
+    <string name="drawer_item_wifidirect">WiFi Direct</string>
+    <string name="fragment_wifidirect_label_me">Me: </string>
+    <string name="fragment_wifidirect_title">NDN Over WiFi Direct</string>
+    <string name="fragment_wifidirect_group_conn_status">Group connection status: </string>
+    <string name="fragment_wifidirect_is_group_own">Is group owner: </string>
+    <string name="fragment_wifidirect_text_group_connected">Connected to group.</string>
+    <string name="fragment_wifidirect_text_group_scanning">Scanning...</string>
+    <string name="fragment_wifidirect_text_group_wifi_p2p_disabled">WIFI p2p is disabled</string>
+    <string name="fragment_wifidirect_curr_ip">Current WiFi Direct IP: </string>
+    <string name="fragment_wifidirect_connected_peers">Connected Peers: </string>
+    <string name="fragment_wifidirect_discovered_peers">Discovered Peers: </string>
+    <string name="fragment_wifidirect_toast_connected_to_group">Currently connected to a group, please disconnect first</string>
+    <string name="fragment_wifidirect_toast_peer_no_longer_available">The peer is no longer available</string>
+    <string name="fragment_wifidirect_toast_connection_works_well">Connection is working correctly</string>
+    <string name="fragment_wifidirect_toast_didnt_get_response">Didn\'t get responses in the last\u0020</string>
+    <string name="fragment_wifidirect_toast_seconds">\u0020seconds</string>
+    <string name="fragment_wifidirect_dialog_cancel_invitation">Cancel invitation for\u0020</string>
+    <string name="fragment_wifidirect_dialog_cancelling">Cancelling</string>
+    <string name="fragment_wifidirect_dialog_cancelling_invitation">Cancelling invitation for\u0020</string>
+    <string name="fragment_wifidirect_dialog_disconnect_from">Disconnect from\u0020</string>
+    <string name="fragment_wifidirect_dialog_disconnecting">Disconnecting</string>
+    <string name="fragment_wifidirect_dialog_disconnecting_from">Disconnecting from\u0020</string>
+    <string name="fragment_wifidirect_dialog_invite">Invite\u0020</string>
+    <string name="fragment_wifidirect_dialog_inviting">Inviting\u0020</string>
+    <string name="fragment_wifidirect_dialog_join_group">\u0020to join a group</string>
+    <string name="fragment_wifidirect_dialog_destroy_group_alter">\u0020Because you are the group owner, disconnecting from a device will also destroy the group.</string>
     <string name="menu_item_delete_setting_item">Delete</string>
     <string name="fragment_logcat_general_operations_category">General Actions</string>
     <string name="fragment_logcat_tags_n_log_levels">Tags &amp; Log Levels</string>