gui: Add ability to view detailed information about routes

Change-Id: Ib284082946287fd3f4927f83ab24f2f82b1021f2
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 945165a..aecc7f9 100644
--- a/app/src/main/java/net/named_data/nfd/MainActivity.java
+++ b/app/src/main/java/net/named_data/nfd/MainActivity.java
@@ -29,6 +29,7 @@
 import android.view.MenuItem;
 
 import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.RibEntry;
 
 import java.util.ArrayList;
 
@@ -38,7 +39,8 @@
 public class MainActivity extends ActionBarActivity
     implements DrawerFragment.DrawerCallbacks,
                LogcatFragment.Callbacks,
-               FaceListFragment.Callbacks
+               FaceListFragment.Callbacks,
+               RouteListFragment.Callbacks
 {
 
   @Override
@@ -174,6 +176,12 @@
     replaceContentFragmentWithBackstack(FaceStatusFragment.newInstance(faceStatus));
   }
 
+  @Override
+  public void onRouteItemSelected(RibEntry ribEntry)
+  {
+    replaceContentFragmentWithBackstack(RouteInfoFragment.newInstance(ribEntry));
+  }
+
   //////////////////////////////////////////////////////////////////////////////
 
   /** Reference to drawer fragment */
diff --git a/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java b/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java
new file mode 100644
index 0000000..bf22848
--- /dev/null
+++ b/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java
@@ -0,0 +1,279 @@
+package net.named_data.nfd;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ListFragment;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.intel.jndn.management.types.FaceStatus;
+import com.intel.jndn.management.types.RibEntry;
+import com.intel.jndn.management.types.Route;
+
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.util.Blob;
+import net.named_data.nfd.utils.G;
+import net.named_data.nfd.utils.Nfdc;
+
+import org.w3c.dom.Text;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RouteInfoFragment extends ListFragment {
+
+  public static RouteInfoFragment
+  newInstance(RibEntry ribEntry) {
+    Bundle args = new Bundle();
+    args.putByteArray(ROUTE_INFORMATION, ribEntry.wireEncode().getImmutableArray());
+
+    RouteInfoFragment fragment = new RouteInfoFragment();
+    fragment.setArguments(args);
+
+    return fragment;
+  }
+
+  @Override
+  public void onAttach(Activity activity)
+  {
+    super.onAttach(activity);
+    try {
+      m_callbacks = (FaceListFragment.Callbacks)activity;
+    } catch (Exception e) {
+      G.Log("Hosting activity must implement FaceListFragment.Callbacks: " + e);
+    }
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState)
+  {
+    super.onCreate(savedInstanceState);
+
+    m_ribEntry = new RibEntry();
+    try {
+      m_ribEntry.wireDecode(new Blob(getArguments().getByteArray(ROUTE_INFORMATION)).buf());
+    }
+    catch (EncodingException e) {
+      G.Log("ROUTE_INFORMATION: EncodingException: " + e);
+    }
+
+    setListAdapter(new RouteFaceListAdapter(getActivity(), m_ribEntry));
+  }
+
+  @SuppressLint("InflateParams")
+  @Override
+  public View onCreateView(LayoutInflater inflater,
+                           @Nullable ViewGroup container,
+                           @Nullable Bundle savedInstanceState)
+  {
+    return inflater.inflate(R.layout.fragment_route_detail, null);
+  }
+
+  @Override
+  public void onViewCreated(View view, Bundle savedInstanceState)
+  {
+    super.onViewCreated(view, savedInstanceState);
+    TextView prefix = (TextView)view.findViewById(R.id.route_detail_prefix);
+    prefix.setText(m_ribEntry.getName().toUri());
+  }
+
+  @Override
+  public void onResume()
+  {
+    super.onResume();
+    m_faceListAsyncTask = new FaceListAsyncTask();
+    m_faceListAsyncTask.execute();
+  }
+
+  @Override
+  public void onPause()
+  {
+    super.onPause();
+    if (m_faceListAsyncTask != null) {
+      m_faceListAsyncTask.cancel(false);
+      m_faceListAsyncTask = null;
+    }
+  }
+
+  @Override
+  public void
+  onListItemClick(ListView l, View v, int position, long id)
+  {
+    if (m_callbacks != null) {
+      RouteFaceListAdapter adapter = (RouteFaceListAdapter)l.getAdapter();
+      if (adapter.m_faces == null)
+        return;
+
+      Route route = adapter.getItem(position);
+      FaceStatus faceStatus = adapter.m_faces.get(route.getFaceId());
+      m_callbacks.onFaceItemSelected(faceStatus);
+    }
+  }
+
+  /////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * Updates the underlying adapter with the given list of FaceStatus.
+   *
+   * Note: This method should only be called from the UI thread.
+   *
+   * @param list Update ListView with the given List<FaceStatus>
+   */
+  private void updateFaceList(List<FaceStatus> list) {
+    ((RouteFaceListAdapter)getListAdapter()).updateFaceList(list);
+  }
+
+
+  private static class RouteFaceListAdapter extends BaseAdapter {
+
+    public RouteFaceListAdapter(Context context, RibEntry ribEntry)
+    {
+      this.m_layoutInflater = LayoutInflater.from(context);
+      this.m_ribEntry = ribEntry;
+    }
+
+    private void
+    updateFaceList(List<FaceStatus> faces)
+    {
+      m_faces = new HashMap<>();
+      for (FaceStatus faceStatus : faces) {
+        m_faces.put(faceStatus.getFaceId(), faceStatus);
+      }
+      notifyDataSetChanged();
+    }
+
+    @Override
+    public int
+    getCount()
+    {
+      return m_ribEntry.getRoutes().size();
+    }
+
+    @Override
+    public Route
+    getItem(int position)
+    {
+      return m_ribEntry.getRoutes().get(position);
+    }
+
+    @Override
+    public long
+    getItemId(int position)
+    {
+      return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      ListItemHolder holder;
+      if (convertView == null) {
+        holder = new ListItemHolder();
+
+        convertView = m_layoutInflater.inflate(R.layout.list_item_route_info_item, null);
+        convertView.setTag(holder);
+
+        holder.m_title = (TextView)convertView.findViewById(R.id.list_item_route_info_title);
+        holder.m_value = (TextView)convertView.findViewById(R.id.list_item_route_info_value);
+      } else {
+        holder = (ListItemHolder)convertView.getTag();
+      }
+
+      Route r = getItem(position);
+      String faceInfo = String.valueOf(r.getFaceId());
+
+      if (m_faces != null) {
+        FaceStatus status = m_faces.get(r.getFaceId());
+        faceInfo += " (" + status.getUri() + ")";
+      }
+
+      holder.m_title.setText(faceInfo);
+      holder.m_value.setText("origin: " + String.valueOf(r.getOrigin()) + " " +
+                               "cost: " + String.valueOf(r.getCost()) + " " +
+                               (r.getFlags().getChildInherit() ? "ChildInherit " : "") +
+                               (r.getFlags().getCapture() ? "Capture " : "")
+                             //            +
+                             //          (r.getExpirationPeriod() > 0 ? "Expires in " + PeriodFormat.getDefault().print(new Period((int)(1000*r.getExpirationPeriod()))) : "")
+      );
+
+      return convertView;
+    }
+
+    private static class ListItemHolder {
+      private TextView m_title;
+      private TextView m_value;
+    }
+
+    /////////////////////////////////////////////////////////////////////////
+    private LayoutInflater m_layoutInflater;
+    private Map<Integer, FaceStatus> m_faces;
+    private RibEntry m_ribEntry;
+  }
+
+  /**
+   * AsyncTask that gets the list of faces from the running NFD.
+   */
+  private class FaceListAsyncTask extends AsyncTask<Void, Void, Pair<List<FaceStatus>, Exception>> {
+    @Override
+    protected void
+    onPreExecute() {
+    }
+
+    @Override
+    protected Pair<List<FaceStatus>, Exception>
+    doInBackground(Void... params) {
+      Exception returnException = null;
+      Nfdc nfdc = new Nfdc();
+      List<FaceStatus> faceStatusList = null;
+      try {
+        faceStatusList = nfdc.faceList();
+      } catch (Exception e) {
+        returnException = e;
+      }
+      nfdc.shutdown();
+      return new Pair<>(faceStatusList, returnException);
+    }
+
+    @Override
+    protected void
+    onCancelled() {
+      // Nothing to do here; No change in UI.
+    }
+
+    @Override
+    protected void
+    onPostExecute(Pair<List<FaceStatus>, Exception> result) {
+      if (result.second != null) {
+        Toast.makeText(getActivity(),
+                       "Error communicating with NFD (" + result.second.getMessage() + ")",
+                       Toast.LENGTH_LONG).show();
+      }
+
+      updateFaceList(result.first);
+    }
+  }
+
+
+  /////////////////////////////////////////////////////////////////////////////
+
+  /** Bundle argument key for face information byte array */
+  private static final String ROUTE_INFORMATION = "net.named_data.nfd.route_information";
+
+  private RibEntry m_ribEntry;
+
+  /** Callback handler of the hosting activity */
+  private FaceListFragment.Callbacks m_callbacks;
+
+  /** Reference to the most recent AsyncTask that was created for listing faces */
+  private FaceListAsyncTask m_faceListAsyncTask;
+}
diff --git a/app/src/main/java/net/named_data/nfd/RouteListFragment.java b/app/src/main/java/net/named_data/nfd/RouteListFragment.java
index 61d39d5..f570545 100644
--- a/app/src/main/java/net/named_data/nfd/RouteListFragment.java
+++ b/app/src/main/java/net/named_data/nfd/RouteListFragment.java
@@ -19,6 +19,7 @@
 
 package net.named_data.nfd;
 
+import android.app.Activity;
 import android.content.Context;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -30,15 +31,18 @@
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.Button;
+import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.intel.jndn.management.types.FaceStatus;
 import com.intel.jndn.management.types.RibEntry;
 import com.intel.jndn.management.types.Route;
 
 import net.named_data.jndn.Name;
 import net.named_data.jndn_xx.util.FaceUri;
+import net.named_data.nfd.utils.G;
 import net.named_data.nfd.utils.Nfdc;
 
 import java.util.ArrayList;
@@ -51,6 +55,27 @@
     return new RouteListFragment();
   }
 
+  public interface Callbacks {
+    /**
+     * This method is called when a route is selected and more
+     * information about it should be presented to the user.
+     *
+     * @param ribEntry RibEntry instance with information about the selected route
+     */
+    public void onRouteItemSelected(RibEntry ribEntry);
+  }
+
+  @Override
+  public void onAttach(Activity activity)
+  {
+    super.onAttach(activity);
+    try {
+      m_callbacks = (Callbacks)activity;
+    } catch (Exception e) {
+      G.Log("Hosting activity must implement this fragment's callbacks: " + e);
+    }
+  }
+
   @Override
   public void
   onCreate(Bundle savedInstanceState) {
@@ -114,6 +139,15 @@
   }
 
   @Override
+  public void onListItemClick(ListView l, View v, int position, long id)
+  {
+    if (m_callbacks != null) {
+      RibEntry ribEntry = ((RouteListAdapter)l.getAdapter()).getItem(position);
+      m_callbacks.onRouteItemSelected(ribEntry);
+    }
+  }
+
+  @Override
   public void
   createRoute(Name prefix, String faceUri)
   {
@@ -361,6 +395,9 @@
 
   /////////////////////////////////////////////////////////////////////////////
 
+  /** Callback handler of the hosting activity */
+  private Callbacks m_callbacks;
+
   /** Reference to the most recent AsyncTask that was created for listing routes */
   private RouteListAsyncTask m_routeListAsyncTask;
 
diff --git a/app/src/main/res/layout/fragment_route_detail.xml b/app/src/main/res/layout/fragment_route_detail.xml
new file mode 100644
index 0000000..048b2c5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_route_detail.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    style="@style/default_linear_layout_padding"
+    >
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/fragment_route_details_title"
+        style="?android:listSeparatorTextViewStyle"
+        />
+
+    <TextView style="@style/default_custom_white_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text=""
+        android:id="@+id/route_detail_prefix"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/fragment_route_details_next_hops"
+        style="?android:listSeparatorTextViewStyle"
+        />
+
+    <include
+        layout="@android:layout/list_content"
+        />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_route_info_item.xml b/app/src/main/res/layout/list_item_route_info_item.xml
new file mode 100644
index 0000000..8c2057a
--- /dev/null
+++ b/app/src/main/res/layout/list_item_route_info_item.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/item_background_activated"
+    >
+
+    <TextView
+        android:id="@+id/list_item_route_info_title"
+        android:text="Title"
+        style="@style/two_row_item_title"
+        />
+
+    <TextView
+        android:id="@+id/list_item_route_info_value"
+        android:text="Value"
+        style="@style/two_row_item_secondary"
+        />
+
+</LinearLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dc86688..87899fe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -54,4 +54,6 @@
 
     <string name="face_add_dialog_create_face">Create face</string>
     <string name="route_add_dialog_create_route">Create route</string>
+    <string name="fragment_route_details_title">Route Details</string>
+    <string name="fragment_route_details_next_hops">List of next hops</string>
 </resources>