From 8b51c26a6b41a5398c4abef170e40186b5a1a5fc Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Mon, 11 Nov 2013 10:02:52 +0000
Subject: [PATCH] Expanded NetDB detail pages to match /netdb

---
 res/layout/fragment_netdb_leaseset_detail.xml |  44 ++++-
 res/layout/fragment_netdb_router_detail.xml   |  51 ++++-
 .../router/activity/NetDbActivity.java        |  10 +-
 .../router/activity/NetDbDetailActivity.java  |  16 +-
 .../router/fragment/NetDbDetailFragment.java  | 186 +++++++++++++++++-
 .../router/fragment/NetDbListFragment.java    |   7 +-
 .../i2p/android/router/loader/NetDbEntry.java |  56 +++++-
 7 files changed, 342 insertions(+), 28 deletions(-)

diff --git a/res/layout/fragment_netdb_leaseset_detail.xml b/res/layout/fragment_netdb_leaseset_detail.xml
index 93575eb7d..87bbdddd9 100644
--- a/res/layout/fragment_netdb_leaseset_detail.xml
+++ b/res/layout/fragment_netdb_leaseset_detail.xml
@@ -14,11 +14,53 @@
         android:textAppearance="?android:attr/textAppearanceMedium" />
 
     <TextView
-        android:id="@+id/dbentry_hash"
+        android:id="@+id/ls_type"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentLeft="true"
         android:layout_below="@+id/ls_nickname"
+        android:text="Destination" />
+
+    <TextView
+        android:id="@+id/dbentry_hash"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/ls_type"
         android:text="LeaseSet hash" />
 
+    <TextView
+        android:id="@+id/label_ls_expiry"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/dbentry_hash"
+        android:text="Expires in:" />
+
+    <TextView
+        android:id="@+id/ls_expiry"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBaseline="@+id/label_ls_expiry"
+        android:layout_alignBottom="@+id/label_ls_expiry"
+        android:layout_alignParentRight="true"
+        android:text="X min" />
+
+    <TextView
+        android:id="@+id/label_leases"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/label_ls_expiry"
+        android:text="Leases:" />
+
+    <LinearLayout
+        android:id="@+id/ls_leases"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/label_leases"
+        android:orientation="vertical" >
+    </LinearLayout>
+
 </RelativeLayout>
diff --git a/res/layout/fragment_netdb_router_detail.xml b/res/layout/fragment_netdb_router_detail.xml
index f0eb57eb1..938cee4f2 100644
--- a/res/layout/fragment_netdb_router_detail.xml
+++ b/res/layout/fragment_netdb_router_detail.xml
@@ -10,4 +10,53 @@
         android:layout_alignParentLeft="true"
         android:text="Router hash" />
 
-</RelativeLayout>
+    <TextView
+        android:id="@+id/label_ri_published"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/dbentry_hash"
+        android:text="Published:" />
+
+    <TextView
+        android:id="@+id/ri_published"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBaseline="@+id/label_ri_published"
+        android:layout_alignBottom="@+id/label_ri_published"
+        android:layout_alignParentRight="true"
+        android:text="X ago" />
+
+    <TextView
+        android:id="@+id/label_ri_addresses"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/label_ri_published"
+        android:text="Address(es):" />
+
+    <LinearLayout
+        android:id="@+id/ri_addresses"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/label_ri_addresses"
+        android:orientation="vertical" >
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/label_ri_stats"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/ri_addresses"
+        android:text="Stats:" />
+
+    <TableLayout
+        android:id="@+id/ri_stats"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/label_ri_stats" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/src/net/i2p/android/router/activity/NetDbActivity.java b/src/net/i2p/android/router/activity/NetDbActivity.java
index e52256482..c4b79ca44 100644
--- a/src/net/i2p/android/router/activity/NetDbActivity.java
+++ b/src/net/i2p/android/router/activity/NetDbActivity.java
@@ -4,7 +4,7 @@ import net.i2p.android.router.R;
 import net.i2p.android.router.fragment.NetDbDetailFragment;
 import net.i2p.android.router.fragment.NetDbListFragment;
 import net.i2p.android.router.fragment.NetDbSummaryPagerFragment;
-import net.i2p.android.router.loader.NetDbEntry;
+import net.i2p.data.Hash;
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
@@ -110,22 +110,22 @@ public class NetDbActivity extends I2PActivityBase implements
 
     // NetDbListFragment.OnEntrySelectedListener
 
-    public void onEntrySelected(NetDbEntry entry) {
+    public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
         if (mTwoPane) {
             // In two-pane mode, show the detail view in this activity by
             // adding or replacing the detail fragment using a
             // fragment transaction.
             NetDbDetailFragment detailFrag = NetDbDetailFragment.newInstance(
-                    entry.isRouterInfo(), entry.getHash());
+                    isRouterInfo, entryHash);
             getSupportFragmentManager().beginTransaction()
                 .replace(R.id.detail_fragment, detailFrag).commit();
         } else {
             // In single-pane mode, simply start the detail activity
             // for the selected item ID.
             Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
-            detailIntent.putExtra(NetDbDetailFragment.IS_RI, entry.isRouterInfo());
+            detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
             detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
-                    entry.getHash().toBase64());
+                    entryHash.toBase64());
             startActivity(detailIntent);
         }
     }
diff --git a/src/net/i2p/android/router/activity/NetDbDetailActivity.java b/src/net/i2p/android/router/activity/NetDbDetailActivity.java
index 62c8f7d1c..b670dcc49 100644
--- a/src/net/i2p/android/router/activity/NetDbDetailActivity.java
+++ b/src/net/i2p/android/router/activity/NetDbDetailActivity.java
@@ -2,13 +2,16 @@ package net.i2p.android.router.activity;
 
 import net.i2p.android.router.R;
 import net.i2p.android.router.fragment.NetDbDetailFragment;
+import net.i2p.android.router.fragment.NetDbListFragment;
 import net.i2p.android.router.service.RouterService;
 import net.i2p.android.router.util.Util;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.Hash;
+import android.content.Intent;
 import android.os.Bundle;
 
-public class NetDbDetailActivity extends I2PActivityBase {
+public class NetDbDetailActivity extends I2PActivityBase implements
+        NetDbListFragment.OnEntrySelectedListener {
     NetDbDetailFragment mDetailFrag;
 
     @Override
@@ -34,4 +37,15 @@ public class NetDbDetailActivity extends I2PActivityBase {
     protected void onRouterBind(RouterService svc) {
         mDetailFrag.onRouterBind();
     }
+
+    // NetDbListFragment.OnEntrySelectedListener
+
+    public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
+        // Start the detail activity for the selected item ID.
+        Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
+        detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
+        detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
+                entryHash.toBase64());
+        startActivity(detailIntent);
+    }
 }
diff --git a/src/net/i2p/android/router/fragment/NetDbDetailFragment.java b/src/net/i2p/android/router/fragment/NetDbDetailFragment.java
index f9f7b6bf0..0b1fac803 100644
--- a/src/net/i2p/android/router/fragment/NetDbDetailFragment.java
+++ b/src/net/i2p/android/router/fragment/NetDbDetailFragment.java
@@ -1,22 +1,36 @@
 package net.i2p.android.router.fragment;
 
+import java.util.Map;
+import java.util.Set;
+
 import net.i2p.android.router.R;
+import net.i2p.android.router.fragment.NetDbListFragment.OnEntrySelectedListener;
 import net.i2p.android.router.loader.NetDbEntry;
 import net.i2p.android.router.util.Util;
 import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
+import net.i2p.data.Lease;
 import net.i2p.data.LeaseSet;
+import net.i2p.data.RouterAddress;
 import net.i2p.data.RouterInfo;
+import android.app.Activity;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TableLayout;
+import android.widget.TableRow;
 import android.widget.TextView;
 
 public class NetDbDetailFragment extends I2PFragmentBase {
     public static final String IS_RI = "is_routerinfo";
     public static final String ENTRY_HASH = "entry_hash";
 
+    OnEntrySelectedListener mEntrySelectedCallback;
     private NetDbEntry mEntry;
 
     public static NetDbDetailFragment newInstance(boolean isRI, Hash hash) {
@@ -28,6 +42,21 @@ public class NetDbDetailFragment extends I2PFragmentBase {
         return f;
     }
 
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        // This makes sure that the container activity has implemented
+        // the callback interface. If not, it throws an exception
+        try {
+            mEntrySelectedCallback = (OnEntrySelectedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement OnEntrySelectedListener");
+        }
+
+    }
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
@@ -60,21 +89,162 @@ public class NetDbDetailFragment extends I2PFragmentBase {
             try {
                 hash.fromBase64(getArguments().getString(ENTRY_HASH));
                 if (getArguments().getBoolean(IS_RI)) {
+                    // Load RouterInfo
                     RouterInfo ri = getNetDb().lookupRouterInfoLocally(hash);
-                    mEntry = NetDbEntry.fromRouterInfo(getRouterContext(), ri);
+                    loadRouterInfo(ri);
                 } else {
+                    // Load LeaseSet
                     LeaseSet ls = getNetDb().lookupLeaseSetLocally(hash);
-                    mEntry = NetDbEntry.fromLeaseSet(getRouterContext(), ls);
-
-                    TextView nickname = (TextView) getView().findViewById(R.id.ls_nickname);
-                    nickname.setText(mEntry.getNickname());
+                    loadLeaseSet(ls);
                 }
-
-                TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
-                entryHash.setText(hash.toBase64());
             } catch (DataFormatException e) {
                 Util.e(e.toString());
             }
         }
     }
+
+    private void loadRouterInfo(RouterInfo ri) {
+        mEntry = NetDbEntry.fromRouterInfo(getRouterContext(), ri);
+
+        if (mEntry.isUs())
+            getActivity().setTitle("Our info");
+        else
+            getActivity().setTitle("Peer info");
+
+        TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
+        entryHash.setText(mEntry.getHash().toBase64());
+
+        if (mEntry.isUs() && getRouter().isHidden()) {
+            TextView pubLabel = (TextView) getView().findViewById(R.id.label_ri_published);
+            pubLabel.setText("Hidden, Updated:");
+        }
+
+        TextView published = (TextView) getView().findViewById(R.id.ri_published);
+        long age = getRouterContext().clock().now() - ri.getPublished();
+        if (age > 0) {
+            published.setText(DataHelper.formatDuration(age) + " ago");
+        } else {
+            // shouldn't happen
+            published.setText(DataHelper.formatDuration(0-age) + " ago???");
+        }
+
+        LinearLayout addresses = (LinearLayout) getView().findViewById(R.id.ri_addresses);
+        for (RouterAddress addr : ri.getAddresses()) {
+            addAddress(addresses, addr);
+        }
+
+        TableLayout stats = (TableLayout) getView().findViewById(R.id.ri_stats);
+        Map<String, String> p = ri.getOptionsMap();
+        for (Map.Entry<String,String> e : (Set<Map.Entry<String,String>>) p.entrySet()) {
+            String key = e.getKey();
+            String val = e.getValue();
+            addTableRow(stats, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
+        }
+    }
+
+    private void addAddress(LinearLayout addresses, RouterAddress addr) {
+        TableLayout table = new TableLayout(getActivity());
+
+        String style = addr.getTransportStyle();
+        addTableRow(table, "Style", style);
+
+        int cost = addr.getCost();
+        if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
+            addTableRow(table, "cost", ""+cost);
+
+        Map<String, String> p = addr.getOptionsMap();
+        for (Map.Entry<String,String> e : (Set<Map.Entry<String,String>>) p.entrySet()) {
+            String key = e.getKey();
+            String val = e.getValue();
+            addTableRow(table, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
+        }
+
+        addresses.addView(table);
+    }
+
+    private void loadLeaseSet(LeaseSet ls) {
+        mEntry = NetDbEntry.fromLeaseSet(getRouterContext(), ls);
+
+        getActivity().setTitle("LeaseSet");
+
+        TextView nickname = (TextView) getView().findViewById(R.id.ls_nickname);
+        nickname.setText(mEntry.getNickname());
+
+        TextView type = (TextView) getView().findViewById(R.id.ls_type);
+        if (mEntry.isLocal()) {
+            if (mEntry.isUnpublished())
+                type.setText("Local Unpublished Destination");
+            else
+                type.setText("Local Destination");
+        }
+
+        TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
+        entryHash.setText(mEntry.getHash().toBase64());
+
+        TextView expiry = (TextView) getView().findViewById(R.id.ls_expiry);
+        long exp = ls.getLatestLeaseDate() - getRouterContext().clock().now();
+        if (exp > 0) {
+            expiry.setText(DataHelper.formatDuration(exp));
+        } else {
+            TextView expiryLabel = (TextView) getView().findViewById(R.id.label_ls_expiry);
+            expiryLabel.setText("Expired:");
+            expiry.setText(DataHelper.formatDuration(exp) + " ago");
+        }
+
+        LinearLayout leases = (LinearLayout) getView().findViewById(R.id.ls_leases);
+        for (int i = 0; i < ls.getLeaseCount(); i++) {
+            Lease lease = ls.getLease(i);
+            addLease(leases, lease, i);
+        }
+    }
+
+    private void addLease(LinearLayout leases, Lease lease, int i) {
+        TableLayout table = new TableLayout(getActivity());
+
+        addTableRow(table, "Lease", ""+(i+1));
+
+        TableRow gateway = new TableRow(getActivity());
+        gateway.setPadding(10, 0, 0, 0);
+
+        TextView gatewayLabel = new TextView(getActivity());
+        gatewayLabel.setText("Gateway");
+
+        Button gatewayButton = new Button(getActivity());
+        gatewayButton.setText(lease.getGateway().toBase64().substring(0, 4));
+        final Hash gatewayHash = lease.getGateway();
+        gatewayButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View view) {
+                mEntrySelectedCallback.onEntrySelected(
+                        true, gatewayHash);
+            }
+        });
+
+        gateway.addView(gatewayLabel);
+        gateway.addView(gatewayButton);
+
+        table.addView(gateway);
+
+        addTableRow(table, "Tunnel", ""+lease.getTunnelId().getTunnelId());
+
+        leases.addView(table);
+    }
+
+    private void addTableRow(TableLayout table, String key, String val) {
+        TableRow row;
+        TextView tl1, tl2;
+
+        row = new TableRow(getActivity());
+        row.setPadding(10, 0, 0, 0);
+
+        tl1 = new TextView(getActivity());
+        tl2 = new TextView(getActivity());
+
+        tl1.setText(key);
+        tl2.setText(val);
+
+        row.addView(tl1);
+        row.addView(tl2);
+
+        table.addView(row);
+    }
 }
diff --git a/src/net/i2p/android/router/fragment/NetDbListFragment.java b/src/net/i2p/android/router/fragment/NetDbListFragment.java
index 1328aabe4..4d336ce70 100644
--- a/src/net/i2p/android/router/fragment/NetDbListFragment.java
+++ b/src/net/i2p/android/router/fragment/NetDbListFragment.java
@@ -6,6 +6,7 @@ import net.i2p.android.router.R;
 import net.i2p.android.router.adapter.NetDbEntryAdapter;
 import net.i2p.android.router.loader.NetDbEntry;
 import net.i2p.android.router.loader.NetDbEntryLoader;
+import net.i2p.data.Hash;
 import net.i2p.router.RouterContext;
 
 import android.app.Activity;
@@ -48,7 +49,7 @@ public class NetDbListFragment extends ListFragment
 
     // Container Activity must implement this interface
     public interface OnEntrySelectedListener {
-        public void onEntrySelected(NetDbEntry entry);
+        public void onEntrySelected(boolean isRouterInfo, Hash entryHash);
     }
 
     @Override
@@ -124,7 +125,9 @@ public class NetDbListFragment extends ListFragment
     @Override
     public void onListItemClick(ListView parent, View view, int pos, long id) {
         super.onListItemClick(parent, view, pos, id);
-        mEntrySelectedCallback.onEntrySelected(mAdapter.getItem(pos));
+        NetDbEntry entry = mAdapter.getItem(pos);
+        mEntrySelectedCallback.onEntrySelected(
+                entry.isRouterInfo(), entry.getHash());
     }
 
     @Override
diff --git a/src/net/i2p/android/router/loader/NetDbEntry.java b/src/net/i2p/android/router/loader/NetDbEntry.java
index 0328f7759..d37131d83 100644
--- a/src/net/i2p/android/router/loader/NetDbEntry.java
+++ b/src/net/i2p/android/router/loader/NetDbEntry.java
@@ -15,21 +15,31 @@ public class NetDbEntry {
     private final boolean mIsRI;
     private final DatabaseEntry mEntry;
 
+    private final boolean mIsUs;
     private final String mCountry;
 
     private final String mNick;
+    private final boolean mLocal;
+    private final boolean mUnpublished;
 
     public static NetDbEntry fromRouterInfo(RouterContext ctx, RouterInfo ri) {
+        Hash us = ctx.routerHash();
+        boolean isUs = ri.getHash().equals(us);
         String country = ctx.commSystem().getCountry(ri.getIdentity().getHash());
-        return new NetDbEntry(true, ri, country, "");
+        return new NetDbEntry(ri, isUs, country);
     }
 
     public static NetDbEntry fromLeaseSet(RouterContext ctx, LeaseSet ls) {
-        String nick;
+        String nick = "";
+        boolean local = false;
+        boolean unpublished = false;
         Destination dest = ls.getDestination();
+        Hash key = dest.calculateHash();
         if (ctx.clientManager().isLocal(dest)) {
-            TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(
-                    dest.calculateHash());
+            local = true;
+            if (! ctx.clientManager().shouldPublishLeaseSet(key))
+                unpublished = true;
+            TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(key);
             if (in != null && in.getDestinationNickname() != null)
                 nick = in.getDestinationNickname();
             else
@@ -41,18 +51,32 @@ public class NetDbEntry {
             else
                 nick = dest.toBase64().substring(0, 6);
         }
-        return new NetDbEntry(false, ls, "", nick);
+        return new NetDbEntry(ls, nick, local, unpublished);
     }
 
-    public NetDbEntry(boolean isRI, DatabaseEntry entry,
-            String country,
-            String nick) {
-        mIsRI = isRI;
-        mEntry = entry;
+    public NetDbEntry(RouterInfo ri,
+            boolean isUs, String country) {
+        mIsRI = true;
+        mEntry = ri;
 
+        mIsUs = isUs;
         mCountry = country;
 
+        mNick = "";
+        mLocal = mUnpublished = false;
+    }
+
+    public NetDbEntry(LeaseSet ls,
+        String nick, boolean local, boolean unpublished) {
+        mIsRI = false;
+        mEntry = ls;
+
         mNick = nick;
+        mLocal = local;
+        mUnpublished = unpublished;
+
+        mIsUs = false;
+        mCountry = "";
     }
 
     public boolean isRouterInfo() {
@@ -67,6 +91,10 @@ public class NetDbEntry {
 
     // RouterInfo-specific methods
 
+    public boolean isUs() {
+        return mIsUs;
+    }
+
     public int getCountryIcon() {
         // http://daniel-codes.blogspot.com/2009/12/dynamically-retrieving-resources-in.html
         try {
@@ -84,4 +112,12 @@ public class NetDbEntry {
     public String getNickname() {
         return mNick;
     }
+
+    public boolean isLocal() {
+        return mLocal;
+    }
+
+    public boolean isUnpublished() {
+        return mUnpublished;
+    }
 }
-- 
GitLab