Support removal of individual next hops - new logic implemented

Refs: #3442

Change-Id: I56f72c9df01fb91c2acb7e037fdff1007def458f
diff --git a/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java b/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java
index f6e0b4f..162f80b 100644
--- a/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java
+++ b/app/src/main/java/net/named_data/nfd/RouteInfoFragment.java
@@ -5,14 +5,21 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
 import android.support.v4.app.ListFragment;
 import android.util.Pair;
+import android.view.ActionMode;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AbsListView;
 import android.widget.BaseAdapter;
 import android.widget.HeaderViewListAdapter;
 import android.widget.ListView;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -21,12 +28,15 @@
 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.encoding.EncodingException;
 import net.named_data.jndn.util.Blob;
+import net.named_data.jndn_xx.util.FaceUri;
 import net.named_data.nfd.utils.G;
 import net.named_data.nfd.utils.NfdcHelper;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -78,6 +88,60 @@
 
     TextView prefix = (TextView)v.findViewById(R.id.route_detail_prefix);
     prefix.setText(m_ribEntry.getName().toUri());
+
+    // Get progress bar spinner view
+    m_reloadingListProgressBar = (ProgressBar)v.findViewById(R.id.route_detail_list_reloading_list_progress_bar);
+
+    ListView listView = getListView();
+    listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+    listView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
+      @Override
+      public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        MenuInflater menuInflater = mode.getMenuInflater();
+        menuInflater.inflate(R.menu.menu_face_list_multiple_modal_menu, menu);
+        return true;
+      }
+
+      @Override
+      public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+      }
+
+      @Override
+      public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        switch (item.getItemId()) {
+          case R.id.menu_item_delete_face_item:
+            G.Log("Requesting to delete " + String.valueOf(m_routeFacesToDelete));
+            removeRouteFace(m_ribEntry.getName(), m_routeFacesToDelete);
+            m_routeFacesToDelete = new HashSet<>();
+            mode.finish();
+            return true;
+          default:
+            return false;
+        }
+      }
+
+      @Override
+      public void onDestroyActionMode(ActionMode mode) {
+
+      }
+
+      @Override
+      public void
+      onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+        if (checked && id < 256) {
+          getListView().setItemChecked(position, false);
+          return;
+        }
+        if (checked)
+          m_routeFacesToDelete.add((int)id);
+        else
+          m_routeFacesToDelete.remove((int)id);
+      }
+
+      private HashSet<Integer> m_routeFacesToDelete = new HashSet<>();
+
+    });
   }
 
   @Override
@@ -103,17 +167,18 @@
   public void onResume()
   {
     super.onResume();
-    m_faceListAsyncTask = new FaceListAsyncTask();
-    m_faceListAsyncTask.execute();
+    startRouteFaceListRetrievalTask();
   }
 
   @Override
   public void onPause()
   {
     super.onPause();
-    if (m_faceListAsyncTask != null) {
-      m_faceListAsyncTask.cancel(false);
-      m_faceListAsyncTask = null;
+    stopRouteFaceListRetrievalTask();
+
+    if (m_routeFaceRemoveAsyncTask != null) {
+      m_routeFaceRemoveAsyncTask.cancel(false);
+      m_routeFaceRemoveAsyncTask = null;
     }
   }
 
@@ -134,6 +199,43 @@
 
   /////////////////////////////////////////////////////////////////////////////
 
+  private void removeRouteFace(Name prefix, HashSet<Integer> faceIds)
+  {
+    m_routeFaceRemoveAsyncTask = new RouteFaceRemoveAsyncTask(prefix, faceIds);
+    m_routeFaceRemoveAsyncTask.execute();
+  }
+
+  private void retrieveRouteFaceList() {
+    // Stop if running; before starting the new Task
+    if (m_routeListAsyncTask != null) {
+      m_routeListAsyncTask.cancel(false);
+      m_routeListAsyncTask = null;
+    }
+    m_routeListAsyncTask = new RouteListAsyncTask();
+    m_routeListAsyncTask.execute();
+
+    stopRouteFaceListRetrievalTask();
+    startRouteFaceListRetrievalTask();
+  }
+
+  /**
+   * Create a new AsyncTask for face list information retrieval.
+   */
+  private void startRouteFaceListRetrievalTask() {
+    m_faceListAsyncTask = new FaceListAsyncTask();
+    m_faceListAsyncTask.execute();
+  }
+
+  /**
+   * Stops a previously started face retrieval AsyncTask.
+   */
+  private void stopRouteFaceListRetrievalTask() {
+    if (m_faceListAsyncTask != null) {
+      m_faceListAsyncTask.cancel(false);
+      m_faceListAsyncTask = null;
+    }
+  }
+
   /**
    * Updates the underlying adapter with the given list of FaceStatus.
    *
@@ -182,7 +284,7 @@
     public long
     getItemId(int position)
     {
-      return position;
+      return m_ribEntry.getRoutes().get(position).getFaceId();
     }
 
     @Override
@@ -238,6 +340,8 @@
     @Override
     protected void
     onPreExecute() {
+      // Display progress bar
+      m_reloadingListProgressBar.setVisibility(View.VISIBLE);
     }
 
     @Override
@@ -258,22 +362,162 @@
     @Override
     protected void
     onCancelled() {
-      // Nothing to do here; No change in UI.
+      // Remove progress bar
+      m_reloadingListProgressBar.setVisibility(View.GONE);
     }
 
     @Override
     protected void
     onPostExecute(Pair<List<FaceStatus>, Exception> result) {
+      // Remove progress bar
+      m_reloadingListProgressBar.setVisibility(View.GONE);
+
       if (result.second != null) {
         Toast.makeText(getActivity(),
                        "Error communicating with NFD (" + result.second.getMessage() + ")",
                        Toast.LENGTH_LONG).show();
+        return;
       }
 
       updateFaceList(result.first);
     }
   }
 
+  private class RouteListAsyncTask extends AsyncTask<Void, Void, Pair<List<RibEntry>, Exception>> {
+    @Override
+    protected void
+    onPreExecute() {
+      // Display progress bar
+      m_reloadingListProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    protected Pair<List<RibEntry>, Exception>
+    doInBackground(Void... params) {
+      NfdcHelper nfdcHelper = new NfdcHelper();
+      Exception returnException = null;
+      List<RibEntry> routes = null;
+      try {
+        routes = nfdcHelper.ribList();
+      }
+      catch (Exception e) {
+        returnException = e;
+      }
+      nfdcHelper.shutdown();
+      return new Pair<>(routes, returnException);
+    }
+
+    @Override
+    protected void onCancelled() {
+      // Remove progress bar
+      m_reloadingListProgressBar.setVisibility(View.GONE);
+    }
+
+    @Override
+    protected void onPostExecute(Pair<List<RibEntry>, Exception> result) {
+      // Remove progress bar
+      m_reloadingListProgressBar.setVisibility(View.GONE);
+
+      if (result.second != null) {
+        Toast.makeText(getActivity(),
+                "Error communicating with NFD (" + result.second.getMessage() + ")",
+                Toast.LENGTH_LONG).show();
+        return;
+      }
+
+      updateRoute(result.first);
+    }
+  }
+
+  private void updateRoute(List<RibEntry> ribList) {
+    for (RibEntry rib : ribList) {
+      if ((rib.getName().toUri()).equals(m_ribEntry.getName().toUri())) {
+        m_ribEntry = rib;
+        m_routeFaceListAdapter = new RouteFaceListAdapter(getActivity(), m_ribEntry);
+        setListAdapter(m_routeFaceListAdapter);
+        return;
+      }
+    }
+
+    // After removal of the last next hops, jump back to the route list fragment
+    Fragment fragment = RouteListFragment.newInstance();
+    getActivity().getSupportFragmentManager()
+              .beginTransaction()
+              .replace(R.id.main_fragment_container, fragment)
+              .commit();
+  }
+
+  /**
+   * AsyncTask that removes next hops that are passed in as a list of NextHopInfo.
+   */
+  private class RouteFaceRemoveAsyncTask extends AsyncTask<Void, Void, String> {
+    public
+    RouteFaceRemoveAsyncTask(Name prefix, HashSet<Integer> routeFaceIds)
+    {
+      m_prefix = prefix;
+      m_routeFaceList = routeFaceIds;
+    }
+
+    @Override
+    protected String
+    doInBackground(Void... params)
+    {
+      NfdcHelper nfdcHelper = new NfdcHelper();
+      try {
+        for (int routeFaceId : m_routeFaceList) {
+          nfdcHelper.ribUnregisterPrefix(m_prefix, routeFaceId);
+        }
+
+        nfdcHelper.shutdown();
+        return "OK";
+      }
+      catch (FaceUri.CanonizeError e) {
+        return "Error Destroying dace (" + e.getMessage() + ")";
+      }
+      catch (FaceUri.Error e) {
+        return "Error destroying face (" + e.getMessage() + ")";
+      }
+      catch (Exception e) {
+        return "Error communicating with NFD (" + e.getMessage() + ")";
+      }
+      finally {
+        nfdcHelper.shutdown();
+      }
+    }
+
+    @Override
+    protected void
+    onPreExecute()
+    {
+      // Display progress bar
+      m_reloadingListProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    protected void
+    onPostExecute(String status)
+    {
+      // Display progress bar
+      m_reloadingListProgressBar.setVisibility(View.VISIBLE);
+      Toast.makeText(getActivity(), status, Toast.LENGTH_LONG).show();
+
+      retrieveRouteFaceList();
+    }
+
+    @Override
+    protected void
+    onCancelled()
+    {
+      // Remove progress bar
+      m_reloadingListProgressBar.setVisibility(View.GONE);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    private Name m_prefix;
+    private HashSet<Integer> m_routeFaceList;
+  }
+
 
   /////////////////////////////////////////////////////////////////////////////
 
@@ -282,11 +526,19 @@
 
   private RibEntry m_ribEntry;
 
+  /** Progress bar spinner to display to user when destroying faces */
+  private ProgressBar m_reloadingListProgressBar;
+
   /** Callback handler of the hosting activity */
   private FaceListFragment.Callbacks m_callbacks;
 
+  /** Reference to the most recent AsyncTask that was created for removing a next hop for a route */
+  private RouteFaceRemoveAsyncTask m_routeFaceRemoveAsyncTask;
+
   /** Reference to the most recent AsyncTask that was created for listing faces */
   private FaceListAsyncTask m_faceListAsyncTask;
 
+  private RouteListAsyncTask m_routeListAsyncTask;
+
   private RouteFaceListAdapter m_routeFaceListAdapter;
 }
diff --git a/app/src/main/res/layout/fragment_route_detail_list_header.xml b/app/src/main/res/layout/fragment_route_detail_list_header.xml
index 539e84a..2244c1a 100644
--- a/app/src/main/res/layout/fragment_route_detail_list_header.xml
+++ b/app/src/main/res/layout/fragment_route_detail_list_header.xml
@@ -29,4 +29,10 @@
         style="?android:listSeparatorTextViewStyle"
         />
 
+    <ProgressBar
+        android:id="@+id/route_detail_list_reloading_list_progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+
 </LinearLayout>
\ No newline at end of file