Hedgeroid: Misguided attempts at getting the connection lifecycle right.
authorMedo <smaxein@googlemail.com>
Tue, 24 Jul 2012 16:57:48 +0200
changeset 7355 5673e95ef647
parent 7352 641f11cdd319
child 7358 57a508884052
Hedgeroid: Misguided attempts at getting the connection lifecycle right. Committing this because it basically runs this way, so i might want to revert to it later :)
project_files/Android-build/SDL-android-project/res/layout/activity_lobby.xml
project_files/Android-build/SDL-android-project/res/layout/tab_indicator.xml
project_files/Android-build/SDL-android-project/res/layout/vertical_tabs.xml
project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml
project_files/Android-build/SDL-android-project/res/values/strings.xml
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TickHandler.java
--- a/project_files/Android-build/SDL-android-project/res/layout/activity_lobby.xml	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/activity_lobby.xml	Tue Jul 24 16:57:48 2012 +0200
@@ -1,36 +1,56 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
-	xmlns:android="http://schemas.android.com/apk/res/android"
-  	android:layout_width="fill_parent"
-	android:layout_height="fill_parent">
-	<include layout="@layout/background"/>
-	<TabHost
-	    android:id="@android:id/tabhost"
-	    android:layout_width="match_parent"
-	    android:layout_height="match_parent">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" >
+
+    <include layout="@layout/background" />
 
-    <LinearLayout
-        android:orientation="vertical"
+    <TabHost
+        android:id="@android:id/tabhost"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal" >
+
+            <TabWidget
+                android:id="@android:id/tabs"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_weight="0" />
 
-        <TabWidget
-            android:id="@android:id/tabs"
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
+            <FrameLayout
+                android:id="@android:id/tabcontent"
+                android:layout_width="0dip"
+                android:layout_height="match_parent"
+                android:layout_weight="1" >
+
+                <fragment
+                    android:id="@+id/roomListFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.RoomlistFragment"
+                    tools:layout="@layout/lobby_rooms_fragment" />
 
-        <FrameLayout
-            android:id="@android:id/tabcontent"
-            android:layout_width="0dp"
-            android:layout_height="0dp"/>
+                <fragment
+                    android:id="@+id/chatFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.LobbyChatFragment"
+                    tools:layout="@layout/lobby_chat_fragment" />
 
-        <android.support.v4.view.ViewPager
-            android:id="@+id/pager"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
+                <fragment
+                    android:id="@+id/playerListFragment"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    class="org.hedgewars.hedgeroid.netplay.PlayerlistFragment"
+                    tools:layout="@layout/lobby_players_fragment" />
+            </FrameLayout>
+        </LinearLayout>
+    </TabHost>
 
-    </LinearLayout>
-</TabHost>
 </FrameLayout>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/tab_indicator.xml	Tue Jul 24 16:57:48 2012 +0200
@@ -0,0 +1,22 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="64dip"
+    android:layout_height="0dip"
+    android:layout_weight="1"
+    android:layout_marginTop="-3dip"
+    android:layout_marginBottom="-3dip"
+    android:orientation="vertical">
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+    />
+
+    <TextView android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        style="?android:attr/tabWidgetStyle"
+    />
+</RelativeLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/layout/vertical_tabs.xml	Tue Jul 24 16:57:48 2012 +0200
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/tabhost"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal" >
+
+        <TabWidget
+            android:id="@android:id/tabs"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="0" />
+
+        <FrameLayout
+            android:id="@android:id/tabcontent"
+            android:layout_width="0dip"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+    </LinearLayout>
+</TabHost>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml	Tue Jul 24 16:57:48 2012 +0200
@@ -3,4 +3,8 @@
         android:id="@+id/room_create"
         android:title="@string/lobby_roomlistmenu_create"
         android:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/disconnect"
+        android:title="@string/lobby_menu_disconnect"
+        android:showAsAction="ifRoom" />
 </menu>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/values/strings.xml	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/values/strings.xml	Tue Jul 24 16:57:48 2012 +0200
@@ -96,6 +96,15 @@
     <string name="lobby_playerlist_contextmenu_follow">Follow</string>
     <string name="lobby_roomlistmenu_create">Create room</string>
     <string name="lobby_roomlistmenu_refresh">Refresh</string>
+    <string name="lobby_menu_disconnect">Disconnect</string>
     
     <string name="not_implemented_yet">Sorry, not implemented yet. :(</string>
+    
+    <!-- Errors -->
+    <string name="error_connection_failed">Unable to connect to the server.</string>
+    <string name="error_unexpected">An unexpected error has occurred: %1$s</string>
+    <string name="error_server_too_old">The server you tried to connect to is using an incompatible protocol.</string>
+    <string name="error_auth_failed">Unable to authenticate for your username.</string>
+    <string name="error_connection_lost">The connection to the server was lost.</string>
+    
 </resources>
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Tue Jul 24 16:57:48 2012 +0200
@@ -25,16 +25,21 @@
 import org.hedgewars.hedgeroid.Downloader.DownloadAssets;
 import org.hedgewars.hedgeroid.Downloader.DownloadListActivity;
 import org.hedgewars.hedgeroid.netplay.LobbyActivity;
+import org.hedgewars.hedgeroid.netplay.NetplayService;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.os.Bundle;
 import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -63,7 +68,11 @@
 		startGame.setOnClickListener(startGameClicker);
 		joinLobby.setOnClickListener(new OnClickListener() {
 			public void onClick(View v) {
-				showDialog(DIALOG_START_NETGAME);
+				if(!NetplayService.isActive()) {
+					showDialog(DIALOG_START_NETGAME);
+				} else {
+					startActivity(new Intent(getApplicationContext(), LobbyActivity.class));
+				}
 			}
 		});
 
@@ -141,10 +150,11 @@
 					edit.putString(PREF_PLAYERNAME, playerName);
 					edit.commit();
 					
-					// TODO actually use that name
-					Intent netplayIntent = new Intent(getApplicationContext(), LobbyActivity.class);
-					netplayIntent.putExtra("playerName", playerName);
-					startActivity(netplayIntent);
+					Intent netplayServiceIntent = new Intent(getApplicationContext(), NetplayService.class);
+					netplayServiceIntent.putExtra(NetplayService.EXTRA_PLAYERNAME, playerName);
+					startService(netplayServiceIntent);
+					
+					LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(connectedReceiver, new IntentFilter(NetplayService.ACTION_CONNECTED));
 				}
 			}
 		});
@@ -170,4 +180,11 @@
 			startActivity(new Intent(getApplicationContext(), StartGameActivity.class));
 		}
 	};
+	
+	private BroadcastReceiver connectedReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			startActivity(new Intent(getApplicationContext(), LobbyActivity.class));
+		}
+	};
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyActivity.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyActivity.java	Tue Jul 24 16:57:48 2012 +0200
@@ -1,51 +1,113 @@
 package org.hedgewars.hedgeroid.netplay;
 
-import java.util.ArrayList;
-
 import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.NetplayService.NetplayBinder;
 
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android.os.IBinder;
 import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.view.ViewPager;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TabHost;
-import android.widget.TabWidget;
+import android.widget.TextView;
+import android.widget.Toast;
 
 public class LobbyActivity extends FragmentActivity {
-    TabHost mTabHost;
-    ViewPager  mViewPager;
-    TabsAdapter mTabsAdapter;
-	
+    private TabHost tabHost;
+    private NetplayService service;
+    
+    private final BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
+		@Override
+		public void onReceive(Context context, Intent intent) {
+			String message = intent.getStringExtra(NetplayService.EXTRA_MESSAGE);
+			Toast.makeText(getApplicationContext(), "Disconnected: "+message, Toast.LENGTH_LONG).show();
+			finish();
+		}
+	};
+    
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(disconnectReceiver, new IntentFilter(NetplayService.ACTION_DISCONNECTED));
+		bindService(new Intent(this, NetplayService.class), serviceConnection, 0);
 
         setContentView(R.layout.activity_lobby);
-        mTabHost = (TabHost)findViewById(android.R.id.tabhost);
-        if(mTabHost != null) {
-	        mTabHost.setup();
-	
-	        mViewPager = (ViewPager)findViewById(R.id.pager);
-	
-	        mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
+        tabHost = (TabHost)findViewById(android.R.id.tabhost);
+        if(tabHost != null) {
+	        tabHost.setup();
+	        tabHost.getTabWidget().setOrientation(LinearLayout.VERTICAL);
+
+	        tabHost.addTab(tabHost.newTabSpec("rooms").setIndicator(createIndicatorView(tabHost, "Rooms", null)).setContent(R.id.roomListFragment));
+	        tabHost.addTab(tabHost.newTabSpec("chat").setIndicator(createIndicatorView(tabHost, "Chat", null)).setContent(R.id.chatFragment));
+	        tabHost.addTab(tabHost.newTabSpec("players").setIndicator(createIndicatorView(tabHost, "Players", null)).setContent(R.id.playerListFragment));
 	
-	        mTabsAdapter.addTab(mTabHost.newTabSpec("roomlist").setIndicator("Rooms"),
-	        		RoomlistFragment.class, null);
-	        mTabsAdapter.addTab(mTabHost.newTabSpec("chat").setIndicator("Chat"),
-	        		LobbyChatFragment.class, null);
-	        mTabsAdapter.addTab(mTabHost.newTabSpec("players").setIndicator("Players"),
-	        		PlayerlistFragment.class, null);
-	
-	        if (savedInstanceState != null) {
-	            mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
+	        if (icicle != null) {
+	            tabHost.setCurrentTabByTag(icicle.getString("currentTab"));
 	        }
         }
     }
     
+    @Override
+    protected void onDestroy() {
+    	super.onDestroy();
+    	LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(disconnectReceiver);
+    	unbindService(serviceConnection);
+    }
+    
+    private View createIndicatorView(TabHost tabHost, CharSequence label, Drawable icon) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        View tabIndicator = inflater.inflate(R.layout.tab_indicator,
+                tabHost.getTabWidget(), // tab widget is the parent
+                false); // no inflate params
+
+        final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
+        tv.setText(label);
+        
+        if(icon != null) {
+	        final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
+	        iconView.setImageDrawable(icon);
+        }
+        
+        return tabIndicator;
+    }
+
+    
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		super.onCreateOptionsMenu(menu);
+		getMenuInflater().inflate(R.menu.lobby_options, menu);
+		return true;
+	}
+	
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+		case R.id.room_create:
+			Toast.makeText(this, R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+			return true;
+		case R.id.disconnect:
+			if(service != null && service.isConnected()) {
+				service.disconnect();
+			}
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
+    
 	/*@Override
 	protected void onCreate(Bundle arg0) {
 		super.onCreate(arg0);
@@ -78,108 +140,20 @@
 	}*/
 	
     @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        if(mTabHost != null) {
-        	outState.putString("tab", mTabHost.getCurrentTabTag());
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if(tabHost != null) {
+        	icicle.putString("currentTab", tabHost.getCurrentTabTag());
         }
     }
-	
-    /**
-     * This is a helper class that implements the management of tabs and all
-     * details of connecting a ViewPager with associated TabHost.  It relies on a
-     * trick.  Normally a tab host has a simple API for supplying a View or
-     * Intent that each tab will show.  This is not sufficient for switching
-     * between pages.  So instead we make the content part of the tab host
-     * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
-     * view to show as the tab content.  It listens to changes in tabs, and takes
-     * care of switch to the correct paged in the ViewPager whenever the selected
-     * tab changes.
-     */
-    public static class TabsAdapter extends FragmentPagerAdapter
-            implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
-        private final Context mContext;
-        private final TabHost mTabHost;
-        private final ViewPager mViewPager;
-        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
-
-        static final class TabInfo {
-            private final Class<?> clss;
-            private final Bundle args;
-
-            TabInfo(Class<?> _class, Bundle _args) {
-                clss = _class;
-                args = _args;
-            }
-        }
-
-        static class DummyTabFactory implements TabHost.TabContentFactory {
-            private final Context mContext;
-
-            public DummyTabFactory(Context context) {
-                mContext = context;
-            }
-
-            public View createTabContent(String tag) {
-                View v = new View(mContext);
-                v.setMinimumWidth(0);
-                v.setMinimumHeight(0);
-                return v;
-            }
+    
+    private ServiceConnection serviceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+        	service = ((NetplayBinder) binder).getService();
         }
 
-        public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
-            super(activity.getSupportFragmentManager());
-            mContext = activity;
-            mTabHost = tabHost;
-            mViewPager = pager;
-            mTabHost.setOnTabChangedListener(this);
-            mViewPager.setAdapter(this);
-            mViewPager.setOnPageChangeListener(this);
-        }
-
-        public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
-            tabSpec.setContent(new DummyTabFactory(mContext));
-
-            TabInfo info = new TabInfo(clss, args);
-            mTabs.add(info);
-            mTabHost.addTab(tabSpec);
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public int getCount() {
-            return mTabs.size();
+        public void onServiceDisconnected(ComponentName className) {
+        	service = null;
         }
-
-        @Override
-        public Fragment getItem(int position) {
-            TabInfo info = mTabs.get(position);
-            return Fragment.instantiate(mContext, info.clss.getName(), info.args);
-        }
-
-        public void onTabChanged(String tabId) {
-            int position = mTabHost.getCurrentTab();
-            mViewPager.setCurrentItem(position);
-        }
-
-        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        }
-
-        public void onPageSelected(int position) {
-            // Unfortunately when TabHost changes the current tab, it kindly
-            // also takes care of putting focus on it when not in touch mode.
-            // The jerk.
-            // This hack tries to prevent this from pulling focus out of our
-            // ViewPager.
-            TabWidget widget = mTabHost.getTabWidget();
-            int oldFocusability = widget.getDescendantFocusability();
-            widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-            mTabHost.setCurrentTab(position);
-            widget.setDescendantFocusability(oldFocusability);
-        }
-
-        public void onPageScrollStateChanged(int state) {
-        }
-    }
+    };
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Tue Jul 24 16:57:48 2012 +0200
@@ -26,13 +26,13 @@
 	private EditText editText;
 	private ListView listView;
 	private ChatlogAdapter adapter;
-	private Netconn netconn;
+	private NetplayService service;
 	
 	private void commitText() {
 		String text = editText.getText().toString();
-		if(netconn != null && netconn.isConnected() && text.length()>0) {
+		if(service != null && service.isConnected() && text.length()>0) {
 			editText.setText("");
-			netconn.sendChat(text);
+			service.sendChat(text);
 		}
 	}
 	
@@ -84,15 +84,15 @@
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
         	Log.d("LobbyChatFragment", "netconn received");
-        	netconn = ((NetplayBinder) binder).getNetconn();
-        	adapter.setLog(netconn.lobbyChatlog.getLog());
-        	netconn.lobbyChatlog.registerObserver(adapter);
+        	service = ((NetplayBinder) binder).getService();
+        	adapter.setLog(service.lobbyChatlog.getLog());
+        	service.lobbyChatlog.registerObserver(adapter);
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
-        	netconn.lobbyChatlog.unregisterObserver(adapter);
-        	netconn = null;
+        	service.lobbyChatlog.unregisterObserver(adapter);
+        	service = null;
         }
     };
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java	Mon Jul 23 00:17:06 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,204 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.hedgewars.hedgeroid.Utils;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.IntStrCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.MetaschemePtr;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.NetconnPtr;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomArrayPtr;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomListCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomPtr;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrRoomCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrStrCallback;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.VoidCallback;
-
-import com.sun.jna.Pointer;
-
-import android.content.Context;
-import android.util.Log;
-
-/**
- * Java-wrapper for the C netconn type. Apart from turning netconn into a more Java-like
- * object with methods, this also handles some of the problems of C <-> Java interop (e.g.
- * ensuring that callback objects don't get garbage collected).
- */
-public class Netconn {
-	private static final JnaFrontlib FLIB = Flib.INSTANCE;
-	private static final String DEFAULT_SERVER = "140.247.62.101";
-	private static final int DEFAULT_PORT = 46631;
-	
-	private NetconnPtr conn;
-	private String playerName;
-	private boolean joined; // True once we have been admitted to the lobby
-	
-	public final PlayerList playerList = new PlayerList();
-	public final RoomList roomList = new RoomList();
-	public final MessageLog lobbyChatlog;
-	public final MessageLog roomChatlog;
-	
-	private StrCallback lobbyJoinCb = new StrCallback() {
-		public void callback(Pointer context, String arg1) {
-			playerList.addPlayerWithNewId(arg1);
-			lobbyChatlog.appendPlayerJoin(arg1);
-		}
-	};
-	
-	private StrStrCallback lobbyLeaveCb = new StrStrCallback() {
-		public void callback(Pointer context, String name, String msg) {
-			playerList.removePlayer(name);
-			lobbyChatlog.appendPlayerLeave(name, msg);
-		}
-	};
-	
-	private StrStrCallback chatCb = new StrStrCallback() {
-		public void callback(Pointer context, String name, String msg) {
-			getCurrentLog().appendChat(name, msg);
-		}
-	};
-	
-	private IntStrCallback messageCb = new IntStrCallback() {
-		public void callback(Pointer context, int type, String msg) {
-			getCurrentLog().appendMessage(type, msg);
-		}
-	};
-	
-	private RoomCallback roomAddCb = new RoomCallback() {
-		public void callback(Pointer context, RoomPtr roomPtr) {
-			roomList.addRoomWithNewId(roomPtr);
-		}
-	};
-	
-	private StrRoomCallback roomUpdateCb = new StrRoomCallback() {
-		public void callback(Pointer context, String name, RoomPtr roomPtr) {
-			roomList.updateRoom(name, roomPtr);
-		}
-	};
-	
-	private StrCallback roomDeleteCb = new StrCallback() {
-		public void callback(Pointer context, String name) {
-			roomList.removeRoom(name);
-		}
-	};
-	
-	private VoidCallback connectedCb = new VoidCallback() {
-		public void callback(Pointer context) {
-			// TODO I guess more needs to happen here...
-			joined = true;
-			FLIB.flib_netconn_send_request_roomlist(conn);
-		}
-	};
-	
-	private RoomListCallback roomlistCb = new RoomListCallback() {
-		public void callback(Pointer context, RoomArrayPtr arg1, int count) {
-			roomList.updateList(arg1.getRooms(count));
-		}
-	};
-	
-	private IntStrCallback disconnectCb = new IntStrCallback() {
-		public void callback(Pointer context, int arg1, String arg2) {
-			FLIB.flib_netconn_destroy(conn);
-			conn = null;
-		}
-	};
-	
-	/**
-	 * Connect to the official Hedgewars server.
-	 * 
-	 * @throws IOException if the metascheme file can't be read or the connection to the server fails
-	 */
-	public Netconn(Context context, String playerName) throws IOException {
-		this(context, playerName, DEFAULT_SERVER, DEFAULT_PORT);
-	}
-	
-	/**
-	 * Connect to the server with the given hostname and port
-	 * 
-	 * @throws IOException if the metascheme file can't be read or the connection to the server fails
-	 */
-	public Netconn(Context context, String playerName, String host, int port) throws IOException {
-		if(playerName == null) {
-			playerName = "Player";
-		}
-		this.playerName = playerName;
-		this.lobbyChatlog = new MessageLog(context);
-		this.roomChatlog = new MessageLog(context);
-		
-		MetaschemePtr meta = null;
-		File dataPath = Utils.getDataPathFile(context);
-		try {
-			String metaschemePath = new File(dataPath, "metasettings.ini").getAbsolutePath();
-			meta = FLIB.flib_metascheme_from_ini(metaschemePath);
-			if(meta == null) {
-				throw new IOException("Missing metascheme");
-			}
-			conn = FLIB.flib_netconn_create(playerName, meta, dataPath.getAbsolutePath(), host, port);
-			if(conn == null) {
-				throw new IOException("Unable to connect to the server");
-			}
-			FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
-			FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
-			FLIB.flib_netconn_onChat(conn, chatCb, null);
-			FLIB.flib_netconn_onMessage(conn, messageCb, null);
-			FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
-			FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
-			FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
-			FLIB.flib_netconn_onConnected(conn, connectedCb, null);
-			FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
-			FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
-		} finally {
-			FLIB.flib_metascheme_release(meta);
-		}
-	}
-	
-	public void disconnect() {
-		if(conn != null) {
-			FLIB.flib_netconn_send_quit(conn, "User quit");
-			FLIB.flib_netconn_destroy(conn);
-			conn = null;
-		}
-	}
-	
-	public void tick() {
-		FLIB.flib_netconn_tick(conn);
-	}
-	
-	public void sendChat(String s) {
-		FLIB.flib_netconn_send_chat(conn, s);
-		if(FLIB.flib_netconn_is_in_room_context(conn)) {
-			roomChatlog.appendChat(playerName, s);
-		} else {
-			lobbyChatlog.appendChat(playerName, s);
-		}
-	}
-	
-	private MessageLog getCurrentLog() {
-		if(FLIB.flib_netconn_is_in_room_context(conn)) {
-			return roomChatlog;
-		} else {
-			return lobbyChatlog;
-		}
-	}
-	
-	public void sendNick(String nick) { FLIB.flib_netconn_send_nick(conn, nick); }
-	public void sendPassword(String password) { FLIB.flib_netconn_send_password(conn, password); }
-	public void sendQuit(String message) { FLIB.flib_netconn_send_quit(conn, message); }
-	public void sendRoomlistRequest() { if(joined) FLIB.flib_netconn_send_request_roomlist(conn); }
-	public void sendPlayerInfoQuery(String name) { FLIB.flib_netconn_send_playerInfo(conn, name); }
-	
-	public boolean isConnected() {
-		return conn != null;
-	}
-	
-	@Override
-	protected void finalize() throws Throwable {
-		if(conn != null) {
-			FLIB.flib_netconn_destroy(conn);
-			Log.e("Netconn", "Leaked Netconn object");
-		}
-	}
-
-}
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java	Tue Jul 24 16:57:48 2012 +0200
@@ -1,66 +1,317 @@
 package org.hedgewars.hedgeroid.netplay;
 
-import java.io.IOException;
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.Utils;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.IntStrCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.MetaschemePtr;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.NetconnPtr;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomArrayPtr;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomListCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomPtr;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrRoomCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrStrCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.VoidCallback;
+
+import com.sun.jna.Pointer;
 
 import android.app.Service;
 import android.content.Intent;
 import android.os.Binder;
-import android.os.CountDownTimer;
 import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
 
 public class NetplayService extends Service {
+	// Parameter extras for starting the service
+	public static final String EXTRA_PLAYERNAME = "playername";
+	public static final String EXTRA_PORT = "port";
+	public static final String EXTRA_HOST = "host";
+	
+	// Extras in broadcasts
+	public static final String EXTRA_MESSAGE = "message";
+	public static final String EXTRA_HAS_ERROR = "hasError";
+	
+	
+	private static final String ACTIONPREFIX = "org.hedgewars.hedgeroid.netconn.";
+	public static final String ACTION_DISCONNECTED = ACTIONPREFIX+"DISCONNECTED";
+	public static final String ACTION_CONNECTED = ACTIONPREFIX+"CONNECTED";
+	
+	private static final JnaFrontlib FLIB = Flib.INSTANCE;
+	public static final String DEFAULT_SERVER = "netserver.hedgewars.org";
+	public static final int DEFAULT_PORT = 46631;
+	
+	private static final long TICK_INTERVAL_MS_BOUND = 100;
+	private static final long TICK_INTERVAL_MS_UNBOUND = 2000;
+	
+	// null if the service is not active. Only updated from the main thread.
+	public static NetplayService instance;
+	
 	private final NetplayBinder binder = new NetplayBinder();
-	public Netconn netconn;
-	private CountDownTimer timer;
+	private TickHandler tickHandler;
+	private LocalBroadcastManager broadcastManager;
+	
+	private String playerName;
+	private NetconnPtr conn;
+	private boolean joined; // True once we have been admitted to the lobby
+	
+	public final PlayerList playerList = new PlayerList();
+	public final RoomList roomList = new RoomList();
+	public MessageLog lobbyChatlog;
+	public MessageLog roomChatlog;
 	
 	@Override
 	public IBinder onBind(Intent intent) {
+		Log.d("NetplayService", "onBind");
+		tickHandler.setInterval(TICK_INTERVAL_MS_BOUND);
 		return binder;
 	}
 	
 	@Override
+	public void onRebind(Intent intent) {
+		Log.d("NetplayService", "onRebind");
+		tickHandler.setInterval(TICK_INTERVAL_MS_BOUND);
+	}
+	
+	@Override
+	public boolean onUnbind(Intent intent) {
+		Log.d("NetplayService", "onUnbind");
+		tickHandler.setInterval(TICK_INTERVAL_MS_UNBOUND);
+		return true;
+	}
+	
+	@Override
+	public int onStartCommand(Intent intent, int flags, int startId) {
+		Log.d("NetplayService", "onStartCommand");
+		if(conn != null) {
+			Log.e("NetplayService", "Attempt to start while running");
+			return START_NOT_STICKY;
+		}
+		joined = false;
+		playerList.clear();
+		roomList.clear();
+		lobbyChatlog.clear();
+		roomChatlog.clear();
+		
+		playerName = intent.getStringExtra(EXTRA_PLAYERNAME);
+		if(playerName == null) playerName = "Player";
+		int port = intent.getIntExtra(EXTRA_PORT, DEFAULT_PORT);
+		String host = intent.getStringExtra(EXTRA_HOST);
+		if(host==null) host = DEFAULT_SERVER;
+		
+		MetaschemePtr meta = null;
+		File dataPath;
+		try {
+			dataPath = Utils.getDataPathFile(getApplicationContext());
+		} catch (FileNotFoundException e) {
+			stopWithError(getString(R.string.sdcard_not_mounted));
+			return START_NOT_STICKY;
+		}
+		String metaschemePath = new File(dataPath, "metasettings.ini").getAbsolutePath();
+		meta = FLIB.flib_metascheme_from_ini(metaschemePath);
+		if(meta == null) {
+			stopWithError(getString(R.string.error_unexpected, "Missing metasettings.ini"));
+			return START_NOT_STICKY;
+		}
+		conn = FLIB.flib_netconn_create(playerName, meta, dataPath.getAbsolutePath(), host, port);
+		if(conn == null) {
+			stopWithError(getString(R.string.error_connection_failed));
+			return START_NOT_STICKY;
+		}
+		FLIB.flib_netconn_onLobbyJoin(conn, lobbyJoinCb, null);
+		FLIB.flib_netconn_onLobbyLeave(conn, lobbyLeaveCb, null);
+		FLIB.flib_netconn_onChat(conn, chatCb, null);
+		FLIB.flib_netconn_onMessage(conn, messageCb, null);
+		FLIB.flib_netconn_onRoomAdd(conn, roomAddCb, null);
+		FLIB.flib_netconn_onRoomUpdate(conn, roomUpdateCb, null);
+		FLIB.flib_netconn_onRoomDelete(conn, roomDeleteCb, null);
+		FLIB.flib_netconn_onConnected(conn, connectedCb, null);
+		FLIB.flib_netconn_onRoomlist(conn, roomlistCb, null);
+		FLIB.flib_netconn_onDisconnected(conn, disconnectCb, null);
+		FLIB.flib_metascheme_release(meta);
+		tickHandler.start();
+		instance = this;
+		return START_NOT_STICKY;
+	}
+	
+	private void stopWithoutError() {
+		Intent intent = new Intent(ACTION_DISCONNECTED);
+		intent.putExtra(EXTRA_HAS_ERROR, false);
+		broadcastManager.sendBroadcast(intent);
+		stopSelf();
+	}
+	
+	private void stopWithError(String userMessage) {
+		Intent intent = new Intent(ACTION_DISCONNECTED);
+		intent.putExtra(EXTRA_MESSAGE, userMessage);
+		intent.putExtra(EXTRA_HAS_ERROR, true);
+		broadcastManager.sendBroadcast(intent);
+		stopSelf();
+	}
+	
+	@Override
 	public void onCreate() {
-		Log.d("NetplayService", "Creating");
-		if(Flib.INSTANCE.flib_init() != 0) {
-			throw new RuntimeException("Unable to start frontlib");
-		}
-		try {
-			netconn = new Netconn(getApplicationContext(), "AndroidTester");
-		} catch (IOException e) {
-			// TODO better handling
-			throw new RuntimeException("Unable to start frontlib", e);
-		}
-    	timer = new CountDownTimer(Long.MAX_VALUE, 50) {
-			@Override
-			public void onTick(long millisUntilFinished) {
-				if(netconn != null && netconn.isConnected()) {
-					netconn.tick();
+		Log.d("NetplayService", "onCreate");
+		broadcastManager = LocalBroadcastManager.getInstance(getApplicationContext());
+		lobbyChatlog = new MessageLog(getApplicationContext());
+		roomChatlog = new MessageLog(getApplicationContext());
+		tickHandler = new TickHandler(getMainLooper(), TICK_INTERVAL_MS_UNBOUND, new Runnable() {
+			public void run() {
+				if(conn != null) {
+					FLIB.flib_netconn_tick(conn);
 				}
 			}
-			
-			@Override
-			public void onFinish() {
-			}
-		};
-		timer.start();
+		});
+		if(Flib.INSTANCE.flib_init() != 0) {
+			stopWithError(getString(R.string.error_unexpected, "Unable to start frontlib"));
+		}
 	}
 	
 	@Override
 	public void onDestroy() {
-		Log.d("NetplayService", "Destroying");
-		timer.cancel();
-		netconn.disconnect();
+		instance = null;
+		Log.d("NetplayService", "onDestroy");
+		tickHandler.stop();
+		if(conn != null) {
+			FLIB.flib_netconn_destroy(conn);
+			conn = null;
+		}
 		Flib.INSTANCE.flib_quit();
 	}
 
 	public class NetplayBinder extends Binder {
-		Netconn getNetconn() {
-            return netconn;
+		NetplayService getService() {
+            return NetplayService.this;
         }
 	}
+	
+	private StrCallback lobbyJoinCb = new StrCallback() {
+		public void callback(Pointer context, String arg1) {
+			playerList.addPlayerWithNewId(arg1);
+			lobbyChatlog.appendPlayerJoin(arg1);
+		}
+	};
+	
+	private StrStrCallback lobbyLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			playerList.removePlayer(name);
+			lobbyChatlog.appendPlayerLeave(name, msg);
+		}
+	};
+	
+	private StrStrCallback chatCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			getCurrentLog().appendChat(name, msg);
+		}
+	};
+	
+	private IntStrCallback messageCb = new IntStrCallback() {
+		public void callback(Pointer context, int type, String msg) {
+			getCurrentLog().appendMessage(type, msg);
+		}
+	};
+	
+	private RoomCallback roomAddCb = new RoomCallback() {
+		public void callback(Pointer context, RoomPtr roomPtr) {
+			roomList.addRoomWithNewId(roomPtr);
+		}
+	};
+	
+	private StrRoomCallback roomUpdateCb = new StrRoomCallback() {
+		public void callback(Pointer context, String name, RoomPtr roomPtr) {
+			roomList.updateRoom(name, roomPtr);
+		}
+	};
+	
+	private StrCallback roomDeleteCb = new StrCallback() {
+		public void callback(Pointer context, String name) {
+			roomList.removeRoom(name);
+		}
+	};
+	
+	private VoidCallback connectedCb = new VoidCallback() {
+		public void callback(Pointer context) {
+			broadcastManager.sendBroadcast(new Intent(ACTION_CONNECTED));
+			joined = true;
+			FLIB.flib_netconn_send_request_roomlist(conn);
+		}
+	};
+	
+	private RoomListCallback roomlistCb = new RoomListCallback() {
+		public void callback(Pointer context, RoomArrayPtr arg1, int count) {
+			roomList.updateList(arg1.getRooms(count));
+		}
+	};
+	
+	private IntStrCallback disconnectCb = new IntStrCallback() {
+		public void callback(Pointer context, int reason, String arg2) {
+			switch(reason) {
+			case JnaFrontlib.NETCONN_DISCONNECT_AUTH_FAILED:
+				stopWithError(getString(R.string.error_auth_failed));
+				break;
+			case JnaFrontlib.NETCONN_DISCONNECT_CONNLOST:
+				stopWithError(getString(R.string.error_connection_lost));
+				break;
+			case JnaFrontlib.NETCONN_DISCONNECT_INTERNAL_ERROR:
+				stopWithError(getString(R.string.error_unexpected, arg2));
+				break;
+			case JnaFrontlib.NETCONN_DISCONNECT_NORMAL:
+				stopWithoutError();
+				break;
+			case JnaFrontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD:
+				stopWithError(getString(R.string.error_server_too_old));
+				break;
+			default:
+				stopWithError(arg2);
+				break;
+			}
+			FLIB.flib_netconn_destroy(conn);
+			conn = null;
+		}
+	};
+	
+	public void disconnect() {
+		if(conn != null) {
+			FLIB.flib_netconn_send_quit(conn, "User quit");
+			FLIB.flib_netconn_destroy(conn);
+			conn = null;
+		}
+		stopWithoutError();
+	}
+	
+	public void sendChat(String s) {
+		FLIB.flib_netconn_send_chat(conn, s);
+		if(FLIB.flib_netconn_is_in_room_context(conn)) {
+			roomChatlog.appendChat(playerName, s);
+		} else {
+			lobbyChatlog.appendChat(playerName, s);
+		}
+	}
+	
+	private MessageLog getCurrentLog() {
+		if(FLIB.flib_netconn_is_in_room_context(conn)) {
+			return roomChatlog;
+		} else {
+			return lobbyChatlog;
+		}
+	}
+	
+	public void sendNick(String nick) { FLIB.flib_netconn_send_nick(conn, nick); }
+	public void sendPassword(String password) { FLIB.flib_netconn_send_password(conn, password); }
+	public void sendQuit(String message) { FLIB.flib_netconn_send_quit(conn, message); }
+	public void sendRoomlistRequest() { if(joined) FLIB.flib_netconn_send_request_roomlist(conn); }
+	public void sendPlayerInfoQuery(String name) { FLIB.flib_netconn_send_playerInfo(conn, name); }
+	
+	public boolean isConnected() {
+		return conn != null;
+	}
 
-	public boolean isConnected() {
-		return netconn!=null;
+	public static boolean isActive() {
+		return instance!=null;
 	}
 }
+
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java	Tue Jul 24 16:57:48 2012 +0200
@@ -22,6 +22,13 @@
 		}
 	}
 
+	public void clear() {
+		if(!players.isEmpty()) {
+			players.clear();
+			notifyChanged();
+		}
+	}
+
 	public Map<String, Player> getMap() {
 		return Collections.unmodifiableMap(players);
 	}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Tue Jul 24 16:57:48 2012 +0200
@@ -4,7 +4,6 @@
 import org.hedgewars.hedgeroid.netplay.NetplayService.NetplayBinder;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Bundle;
@@ -21,14 +20,13 @@
 import android.widget.AdapterView.AdapterContextMenuInfo;
 
 public class PlayerlistFragment extends ListFragment {
-	private Netconn netconn;
+	private NetplayService netplayService;
 	private PlayerListAdapter playerListAdapter;
 	
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
-		getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection,
-	            Context.BIND_AUTO_CREATE);
+		getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection, 0);
 		playerListAdapter = new PlayerListAdapter(getActivity());
 		setListAdapter(playerListAdapter);
 	}
@@ -53,8 +51,8 @@
 		switch(item.getItemId()) {
 		case R.id.player_info:
 			Player p = playerListAdapter.getItem(info.position);
-			if(netconn != null) {
-				netconn.sendPlayerInfoQuery(p.name);
+			if(netplayService != null) {
+				netplayService.sendPlayerInfoQuery(p.name);
 			}
 			return true;
 		case R.id.player_follow:
@@ -79,14 +77,14 @@
 	
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
-        	netconn = ((NetplayBinder) binder).getNetconn();
-        	playerListAdapter.setList(netconn.playerList);
+        	netplayService = ((NetplayBinder) binder).getService();
+        	playerListAdapter.setList(netplayService.playerList);
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
         	playerListAdapter.invalidate();
-        	netconn = null;
+        	netplayService = null;
         }
     };
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java	Tue Jul 24 16:57:48 2012 +0200
@@ -54,6 +54,13 @@
 		}
 	}
 	
+	public void clear() {
+		if(!rooms.isEmpty()) {
+			rooms.clear();
+			notifyChanged();
+		}
+	}
+	
 	public Map<String, Room> getMap() {
 		return Collections.unmodifiableMap(rooms);
 	}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java	Mon Jul 23 00:17:06 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java	Tue Jul 24 16:57:48 2012 +0200
@@ -24,13 +24,13 @@
 public class RoomlistFragment extends ListFragment implements OnItemClickListener {
 	private static final int AUTO_REFRESH_INTERVAL_MS = 15000;
 	
-	private Netconn netconn;
+	private NetplayService service;
 	private RoomListAdapter adapter;
 	private CountDownTimer autoRefreshTimer = new CountDownTimer(Long.MAX_VALUE, AUTO_REFRESH_INTERVAL_MS) {
 		@Override
 		public void onTick(long millisUntilFinished) {
-			if(netconn != null && netconn.isConnected()) {
-				netconn.sendRoomlistRequest();
+			if(service != null && service.isConnected()) {
+				service.sendRoomlistRequest();
 			}
 		}
 		
@@ -64,8 +64,8 @@
 	@Override
 	public void onResume() {
 		super.onResume();
-		if(netconn != null) {
-			netconn.sendRoomlistRequest();
+		if(service != null) {
+			service.sendRoomlistRequest();
 			autoRefreshTimer.start();
 		}
 	}
@@ -92,8 +92,8 @@
 	public boolean onOptionsItemSelected(MenuItem item) {
 		switch(item.getItemId()) {
 		case R.id.roomlist_refresh:
-			if(netconn != null) {
-				netconn.sendRoomlistRequest();
+			if(service != null && service.isConnected()) {
+				service.sendRoomlistRequest();
 			}
 			return true;
 		default:
@@ -107,15 +107,15 @@
 	
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
-        	netconn = ((NetplayBinder) binder).getNetconn();
-        	adapter.setList(netconn.roomList);
+        	service = ((NetplayBinder) binder).getService();
+        	adapter.setList(service.roomList);
         	autoRefreshTimer.start();
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
         	adapter.invalidate();
-        	netconn = null;
+        	service = null;
         }
     };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TickHandler.java	Tue Jul 24 16:57:48 2012 +0200
@@ -0,0 +1,54 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * This class handles regularly calling a specified runnable
+ * on the looper provided in the constructor. The first call
+ * occurs without delay (though still via the looper), all
+ * following calls are delayed by (approximately) the interval.
+ * The interval can be changed at any time, which will cause
+ * an immediate execution of the runnable again.
+ */
+class TickHandler extends Handler {
+	private final Runnable callback;
+	private int messageId;
+	private long interval;
+	private boolean running;
+	
+	public TickHandler(Looper looper, long interval, Runnable callback) {
+		super(looper);
+		this.callback = callback;
+		this.interval = interval;
+	}
+	
+	public synchronized void stop() {
+		messageId++;
+		running = false;
+	}
+	
+	public synchronized void start() {
+		messageId++;
+		sendMessage(obtainMessage(messageId));
+		running = true;
+	}
+	
+	public synchronized void setInterval(long interval) {
+		this.interval = interval;
+		if(running) {
+			start();
+		}
+	}
+	
+	@Override
+	public synchronized void handleMessage(Message msg) {
+		if(msg.what == messageId) {
+			callback.run();
+		}
+		if(msg.what == messageId) {
+			sendMessageDelayed(obtainMessage(messageId), interval);
+		}
+	}
+}
\ No newline at end of file