From ae2fa4dce7667e4eab09c17127dd02296a54d4d9 Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Sun, 18 Aug 2013 12:46:13 +0000
Subject: [PATCH] Fill I2PTunnel list with a Loader

Until changes to the list of TunnelControllers can be monitored, the Loader
refreshes the view every 10 seconds.
---
 .../router/fragment/I2PTunnelFragment.java    |  57 ++++---
 .../router/loader/TunnelControllerLoader.java | 147 ++++++++++++++++++
 2 files changed, 184 insertions(+), 20 deletions(-)
 create mode 100644 src/net/i2p/android/router/loader/TunnelControllerLoader.java

diff --git a/src/net/i2p/android/router/fragment/I2PTunnelFragment.java b/src/net/i2p/android/router/fragment/I2PTunnelFragment.java
index aa61338a0..096e3a4d6 100644
--- a/src/net/i2p/android/router/fragment/I2PTunnelFragment.java
+++ b/src/net/i2p/android/router/fragment/I2PTunnelFragment.java
@@ -1,23 +1,30 @@
 package net.i2p.android.router.fragment;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import net.i2p.android.router.R;
 import net.i2p.android.router.adapter.TunnelControllerAdapter;
+import net.i2p.android.router.loader.TunnelControllerLoader;
 import net.i2p.i2ptunnel.TunnelController;
 import net.i2p.i2ptunnel.TunnelControllerGroup;
 import android.os.Bundle;
 import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
-public class I2PTunnelFragment extends ListFragment {
+public class I2PTunnelFragment extends ListFragment
+        implements LoaderManager.LoaderCallbacks<List<TunnelController>> {
     public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
 
+    private static final int CLIENT_LOADER_ID = 1;
+    private static final int SERVER_LOADER_ID = 2;
+
     private TunnelControllerGroup mGroup;
     private TunnelControllerAdapter mAdapter;
+    private boolean mClientTunnels;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -29,6 +36,7 @@ public class I2PTunnelFragment extends ListFragment {
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         mAdapter = new TunnelControllerAdapter(getActivity());
+        mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
 
         String error;
         try {
@@ -39,36 +47,45 @@ public class I2PTunnelFragment extends ListFragment {
             error = iae.toString();
         }
 
-        boolean clientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
         if (mGroup == null) {
             setEmptyText(error);
         } else {
-            if (clientTunnels)
+            if (mClientTunnels)
                 setEmptyText("No configured client tunnels.");
             else
                 setEmptyText("No configured server tunnels.");
         }
-        mAdapter.setData(getControllers(clientTunnels));
+
         setListAdapter(mAdapter);
+        setListShown(false);
+
+        getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
+                : SERVER_LOADER_ID, null, this);
+    }
+
+    public Loader<List<TunnelController>> onCreateLoader(int id, Bundle args) {
+        return new TunnelControllerLoader(getActivity(), mGroup, mClientTunnels);
     }
 
-    private List<TunnelController> getControllers(boolean clientTunnels) {
-        List<TunnelController> ret = new ArrayList<TunnelController>();
-        for (TunnelController controller : mGroup.getControllers())
-            if ( (clientTunnels && isClient(controller.getType())) ||
-                 (!clientTunnels && !isClient(controller.getType())) )
-                ret.add(controller);
-        return ret;
+    public void onLoadFinished(Loader<List<TunnelController>> loader,
+            List<TunnelController> data) {
+        if (loader.getId() == (mClientTunnels ?
+                CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
+            mAdapter.setData(data);
+
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
+        }
     }
 
-    private static boolean isClient(String type) {
-        return ( ("client".equals(type)) ||
-                 ("httpclient".equals(type)) ||
-                 ("sockstunnel".equals(type)) ||
-                 ("socksirctunnel".equals(type)) ||
-                 ("connectclient".equals(type)) ||
-                 ("streamrclient".equals(type)) ||
-                 ("ircclient".equals(type)));
+    public void onLoaderReset(Loader<List<TunnelController>> loader) {
+        if (loader.getId() == (mClientTunnels ?
+                CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
+            mAdapter.setData(null);
+        }
     }
 
     @Override
diff --git a/src/net/i2p/android/router/loader/TunnelControllerLoader.java b/src/net/i2p/android/router/loader/TunnelControllerLoader.java
new file mode 100644
index 000000000..6e43fe137
--- /dev/null
+++ b/src/net/i2p/android/router/loader/TunnelControllerLoader.java
@@ -0,0 +1,147 @@
+package net.i2p.android.router.loader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.i2p.i2ptunnel.TunnelController;
+import net.i2p.i2ptunnel.TunnelControllerGroup;
+
+import android.content.Context;
+import android.os.Handler;
+import android.support.v4.content.AsyncTaskLoader;
+
+public class TunnelControllerLoader extends AsyncTaskLoader<List<TunnelController>> {
+    private TunnelControllerGroup mGroup;
+    private boolean mClientTunnels;
+    private List<TunnelController> mData;
+    private Handler mHandler;
+    private TunnelControllerMonitor mMonitor;
+
+    public TunnelControllerLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
+        super(context);
+        mGroup = tcg;
+        mClientTunnels = clientTunnels;
+        mHandler = new Handler();
+    }
+
+    @Override
+    public List<TunnelController> loadInBackground() {
+        List<TunnelController> ret = new ArrayList<TunnelController>();
+        for (TunnelController controller : mGroup.getControllers())
+            if ( (mClientTunnels && isClient(controller.getType())) ||
+                 (!mClientTunnels && !isClient(controller.getType())) )
+                ret.add(controller);
+        return ret;
+    }
+
+    private static boolean isClient(String type) {
+        return ( ("client".equals(type)) ||
+                 ("httpclient".equals(type)) ||
+                 ("sockstunnel".equals(type)) ||
+                 ("socksirctunnel".equals(type)) ||
+                 ("connectclient".equals(type)) ||
+                 ("streamrclient".equals(type)) ||
+                 ("ircclient".equals(type)));
+    }
+
+    @Override
+    public void deliverResult(List<TunnelController> data) {
+        if (isReset()) {
+            // The Loader has been reset; ignore the result and invalidate the data.
+            if (data != null) {
+                releaseResources(data);
+                return;
+            }
+        }
+
+        // Hold a reference to the old data so it doesn't get garbage collected.
+        // We must protect it until the new data has been delivered.
+        List<TunnelController> oldData = mData;
+        mData = data;
+
+        if (isStarted()) {
+            // If the Loader is in a started state, have the superclass deliver the
+            // results to the client.
+            super.deliverResult(data);
+        }
+
+        // Invalidate the old data as we don't need it any more.
+        if (oldData != null && oldData != data) {
+            releaseResources(oldData);
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        if (mData != null) {
+            // Deliver any previously loaded data immediately.
+            deliverResult(mData);
+        }
+
+        // Begin monitoring the underlying data source.
+        mMonitor = new TunnelControllerMonitor();
+        mHandler.postDelayed(mMonitor, 50);
+
+        if (takeContentChanged() || mData == null) {
+            // When the observer detects a change, it should call onContentChanged()
+            // on the Loader, which will cause the next call to takeContentChanged()
+            // to return true. If this is ever the case (or if the current data is
+            // null), we force a new load.
+            forceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        // The Loader is in a stopped state, so we should attempt to cancel the 
+        // current load (if there is one).
+        cancelLoad();
+
+        // Note that we leave the observer as is. Loaders in a stopped state
+        // should still monitor the data source for changes so that the Loader
+        // will know to force a new load if it is ever started again.
+    }
+
+    @Override
+    protected void onReset() {
+        // Ensure the loader has been stopped.
+        onStopLoading();
+
+        // At this point we can release the resources associated with 'mData'.
+        if (mData != null) {
+            releaseResources(mData);
+            mData = null;
+        }
+
+        // The Loader is being reset, so we should stop monitoring for changes.
+        if (mMonitor != null) {
+            mHandler.removeCallbacks(mMonitor);
+            mMonitor = null;
+        }
+    }
+
+    @Override
+    public void onCanceled(List<TunnelController> data) {
+        // Attempt to cancel the current asynchronous load.
+        super.onCanceled(data);
+
+        // The load has been canceled, so we should release the resources
+        // associated with 'data'.
+        releaseResources(data);
+    }
+
+    private void releaseResources(List<TunnelController> data) {
+        // For a simple List, there is nothing to do. For something like a Cursor, we 
+        // would close it in this method. All resources associated with the Loader
+        // should be released here.
+    }
+
+    private class TunnelControllerMonitor implements Runnable {
+        public void run() {
+            // There is no way (yet) to monitor for changes to the list of
+            // TunnelControllers, so just force a refresh every 10 seconds.
+            onContentChanged();
+            mHandler.postDelayed(this, 10 * 1000);
+        }
+    }
+}
-- 
GitLab