Hedgeroid: Chat and player list work now, but everything is very much WIP
authorMedo <smaxein@googlemail.com>
Tue, 17 Jul 2012 22:27:16 +0200
changeset 7332 3f2e130f9715
parent 7330 867e4fda496e
child 7334 66a10ae88457
Hedgeroid: Chat and player list work now, but everything is very much WIP
project_files/Android-build/SDL-android-project/AndroidManifest.xml
project_files/Android-build/SDL-android-project/res/values/strings.xml
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.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/MessageLog.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/Player.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/PlayerListAdapter.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/Room.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Signal.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TestActivity.java
--- a/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Tue Jul 17 22:27:16 2012 +0200
@@ -47,7 +47,8 @@
             android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
 
         <service android:name=".Downloader.DownloadService" />
-
+		<service android:name=".netplay.NetplayService" />
+		
         <activity
             android:name="StartGameActivity"
             android:label="@string/app_name"
--- a/project_files/Android-build/SDL-android-project/res/values/strings.xml	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/values/strings.xml	Tue Jul 17 22:27:16 2012 +0200
@@ -65,4 +65,9 @@
     
     <!-- Player list -->
     <string name="no_players_in_list">No players</string>
+    
+    <!-- Chatlog messages -->
+    <string name="log_player_join">%1$s has joined.</string>
+    <string name="log_player_leave">%1$s has left.</string>
+    <string name="log_player_leave_with_msg">%1$s has left (%2$s).</string>
 </resources>
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Tue Jul 17 22:27:16 2012 +0200
@@ -30,6 +30,7 @@
 import java.util.List;
 
 import android.annotation.TargetApi;
+import android.app.Application;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.java	Tue Jul 17 22:27:16 2012 +0200
@@ -1,12 +1,21 @@
 package org.hedgewars.hedgeroid.netplay;
 import java.nio.Buffer;
+import java.util.Collections;
 
 import com.sun.jna.Callback;
 import com.sun.jna.Library;
+import com.sun.jna.Native;
 import com.sun.jna.NativeLong;
 import com.sun.jna.Pointer;
 import com.sun.jna.PointerType;
+import com.sun.jna.Structure;
 
+class Flib {
+	static {
+		System.loadLibrary("SDL_net");
+	}
+	public static final JnaFrontlib INSTANCE = (JnaFrontlib)Native.loadLibrary("frontlib", JnaFrontlib.class, Collections.singletonMap(Library.OPTION_TYPE_MAPPER, FrontlibTypeMapper.INSTANCE));
+}
 
 public interface JnaFrontlib extends Library {
 	static final int NETCONN_STATE_CONNECTING = 0;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Tue Jul 17 22:27:16 2012 +0200
@@ -1,11 +1,20 @@
 package org.hedgewars.hedgeroid.netplay;
 
+
 import org.hedgewars.hedgeroid.R;
+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.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.support.v4.app.Fragment;
-import android.text.Html;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -21,29 +30,32 @@
 	private TextView textView;
 	private EditText editText;
 	private ScrollView scrollView;
+	private Netconn netconn;
+	
+	private void scrollDown() {
+		scrollView.post(new Runnable() {
+			public void run() {
+				scrollView.smoothScrollTo(0, textView.getBottom());
+			}
+		});
+	}
 	
 	private void commitText() {
-		String text = editText.getText().toString();
-		int overhang = textView.getHeight()-scrollView.getHeight();
-		boolean followBottom = overhang<=0 || Math.abs(overhang-scrollView.getScrollY())<5;
-		textView.append(Html.fromHtml("<b>Chatter:</b> " + text + "<br/>"));
-		editText.setText("");
-		if(followBottom) {
-			new Handler().post(new Runnable() {
-				public void run() {
-					scrollView.fullScroll(ScrollView.FOCUS_DOWN);
-				}
-			});
+		if(netconn != null && netconn.isConnected()) {
+			String text = editText.getText().toString();
+			editText.setText("");
+			netconn.sendChat(text);
 		}
 	}
-	/*
+	
 	@Override
 	public void onStart() {
 		super.onStart();
 		getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection,
 	            Context.BIND_AUTO_CREATE);
+		Log.d("LobbyChatFragment", "started");
 	}
-	*/
+	
 	@Override
 	public View onCreateView(LayoutInflater inflater, ViewGroup container,
 			Bundle savedInstanceState) {
@@ -52,6 +64,8 @@
 		editText = (EditText) view.findViewById(R.id.lobbyChatInput);
 		scrollView = (ScrollView) view.findViewById(R.id.lobbyConsoleScroll);
 		
+		textView.setMovementMethod(LinkMovementMethod.getInstance());
+		
         editText.setOnEditorActionListener(new OnEditorActionListener() {
 			public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 				boolean handled = false;
@@ -62,24 +76,40 @@
 				return handled;
 			}
 		});
-		
+        
 		return view;
 	}
-	/*
+	
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		getActivity().unbindService(serviceConnection);
+	}
+
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
-        	netplayService = ((NetplayBinder) binder).getService();
-        	try {
-				netplayService.connect("AndroidChatter");
-			} catch (IOException e) {
-				throw new RuntimeException(e);
-			}
+        	Log.d("LobbyChatFragment", "netconn received");
+        	netconn = ((NetplayBinder) binder).getNetconn();
+        	netconn.lobbyLog.observe(observer);
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
-        	netplayService = null;
+        	netconn.lobbyLog.unobserve(observer);
+        	netconn = null;
         }
     };
-    */
+    
+    private MessageLog.Observer observer = new MessageLog.Observer() {
+		public void textChanged(Spanned text) {
+			if(textView != null) {
+				int overhang = textView.getHeight()-scrollView.getHeight();
+				boolean followBottom = overhang<=0 || Math.abs(overhang-scrollView.getScrollY())<5;
+				textView.setText(text);
+				if(followBottom) {
+					scrollDown();
+				}
+			}
+		}
+	};
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/MessageLog.java	Tue Jul 17 22:27:16 2012 +0200
@@ -0,0 +1,139 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.hedgewars.hedgeroid.R;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+
+public class MessageLog {
+	private static final int BACKLOG_CHARS = 10000;
+	
+	private static final int INFO_COLOR = Color.GRAY;
+	private static final int CHAT_COLOR = Color.GREEN;
+	private static final int MECHAT_COLOR = Color.CYAN;
+	private static final int WARN_COLOR = Color.RED;
+	private static final int ERROR_COLOR = Color.RED;
+	
+	private final Context context;
+	private List<Observer> observers = new LinkedList<Observer>();
+	
+	private SpannableStringBuilder log = new SpannableStringBuilder();
+	private List<Integer> lineLengths = new LinkedList<Integer>();
+	
+	public MessageLog(Context context) {
+		this.context = context;
+	}
+	
+	private Spanned makeLogTime() {
+		String time = DateFormat.getTimeFormat(context).format(new Date());
+		return withColor("[" + time + "] ", INFO_COLOR);
+	}
+	
+	private static Spanned span(CharSequence s, Object o) {
+		Spannable spannable = new SpannableString(s);
+		spannable.setSpan(o, 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+		return spannable;
+	}
+	
+	private static Spanned withColor(CharSequence s, int color) {
+		return span(s, new ForegroundColorSpan(color));
+	}
+	
+	private static Spanned bold(CharSequence s) {
+		return span(s, new StyleSpan(Typeface.BOLD));
+	}
+	
+	private void append(CharSequence msg) {
+		SpannableStringBuilder ssb = new SpannableStringBuilder();
+		ssb.append(makeLogTime()).append(msg).append("\n");
+		appendRaw(ssb);
+	}
+	
+	private void appendRaw(CharSequence msg) {
+		lineLengths.add(msg.length());
+		log.append(msg);
+		while(log.length() > BACKLOG_CHARS) {
+			log.delete(0, lineLengths.remove(0));
+		}
+		for(Observer o : observers) {
+			o.textChanged(log);
+		}
+	}
+	
+	void appendPlayerJoin(String playername) {
+		append(withColor("***" + context.getResources().getString(R.string.log_player_join, playername), INFO_COLOR));
+	}
+	
+	void appendPlayerLeave(String playername, String partMsg) {
+		String msg = "***";
+		if(partMsg != null) {
+			msg += context.getResources().getString(R.string.log_player_leave_with_msg, playername, partMsg);
+		} else {
+			msg += context.getResources().getString(R.string.log_player_leave, playername);
+		}
+		append(withColor(msg, INFO_COLOR));
+	}
+	
+	void appendChat(String playerName, String msg) {
+		if(msg.startsWith("/me ")) {
+			append(withColor("*"+playerName+" "+msg, MECHAT_COLOR));
+		} else {
+			Spanned name = bold(playerName+": ");
+			Spanned fullMessage = withColor(TextUtils.concat(name, msg), CHAT_COLOR);
+			append(fullMessage);			
+		}
+	}
+	
+	void appendMessage(int type, String msg) {
+		switch(type) {
+		case JnaFrontlib.NETCONN_MSG_TYPE_ERROR:
+			append(withColor("***"+msg, ERROR_COLOR));
+			break;
+		case JnaFrontlib.NETCONN_MSG_TYPE_WARNING:
+			append(withColor("***"+msg, WARN_COLOR));
+			break;
+		case JnaFrontlib.NETCONN_MSG_TYPE_PLAYERINFO:
+			// TODO better formatting or different way to display
+			append(msg);
+			break;
+		case JnaFrontlib.NETCONN_MSG_TYPE_SERVERMESSAGE:
+			appendRaw(span(TextUtils.concat("\n", Html.fromHtml(msg), "\n\n"), new RelativeSizeSpan(1.5f)));
+			break;
+		default:
+			Log.e("MessageLog", "Unknown messagetype "+type);
+		}
+	}
+	
+	void clear() {
+		log.clear();
+		lineLengths.clear();
+	}
+	
+	public void observe(Observer o) {
+		observers.add(o);
+	}
+	
+	public void unobserve(Observer o) {
+		observers.remove(o);
+	}
+	
+	public static interface Observer {
+		void textChanged(Spanned text);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java	Tue Jul 17 22:27:16 2012 +0200
@@ -0,0 +1,148 @@
+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.StrCallback;
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.StrStrCallback;
+
+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;
+	
+	public final PlayerList playerList = new PlayerList();
+	public final MessageLog lobbyLog;
+	public final MessageLog roomLog;
+	
+	private StrCallback lobbyJoinCb = new StrCallback() {
+		public void callback(Pointer context, String arg1) {
+			playerList.addPlayer(arg1);
+			lobbyLog.appendPlayerJoin(arg1);
+		}
+	};
+	
+	private StrStrCallback lobbyLeaveCb = new StrStrCallback() {
+		public void callback(Pointer context, String name, String msg) {
+			playerList.removePlayer(name);
+			lobbyLog.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);
+		}
+	};
+	
+	/**
+	 * 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.lobbyLog = new MessageLog(context);
+		this.roomLog = 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);
+		} 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)) {
+			roomLog.appendChat(playerName, s);
+		} else {
+			lobbyLog.appendChat(playerName, s);
+		}
+	}
+	
+	private MessageLog getCurrentLog() {
+		if(FLIB.flib_netconn_is_in_room_context(conn)) {
+			return roomLog;
+		} else {
+			return lobbyLog;
+		}
+	}
+	
+	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 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 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java	Tue Jul 17 22:27:16 2012 +0200
@@ -1,16 +1,6 @@
 package org.hedgewars.hedgeroid.netplay;
 
-import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.util.Collections;
-
-import org.hedgewars.hedgeroid.Utils;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.MetaschemePtr;
-import org.hedgewars.hedgeroid.netplay.JnaFrontlib.NetconnPtr;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
 
 import android.app.Service;
 import android.content.Intent;
@@ -19,15 +9,9 @@
 import android.os.IBinder;
 
 public class NetplayService extends Service {
-	static {
-		System.loadLibrary("SDL_net");
-	}
-	public static final JnaFrontlib FRONTLIB = (JnaFrontlib)Native.loadLibrary("frontlib", JnaFrontlib.class, Collections.singletonMap(Library.OPTION_TYPE_MAPPER, FrontlibTypeMapper.INSTANCE));
-
 	private final NetplayBinder binder = new NetplayBinder();
-	public NetconnPtr netconn;
+	public Netconn netconn;
 	private CountDownTimer timer;
-	private String playerName;
 	
 	@Override
 	public IBinder onBind(Intent intent) {
@@ -36,82 +20,43 @@
 	
 	@Override
 	public void onCreate() {
-		if(FRONTLIB.flib_init() != 0) {
+		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");
 		}
+    	timer = new CountDownTimer(Long.MAX_VALUE, 50) {
+			@Override
+			public void onTick(long millisUntilFinished) {
+				if(netconn != null) {
+					netconn.tick();
+				}
+			}
+			
+			@Override
+			public void onFinish() {
+			}
+		};
+		timer.start();
 	}
 	
 	@Override
 	public void onDestroy() {
-		disconnect();
-		FRONTLIB.flib_quit();
+		netconn.disconnect();
+		Flib.INSTANCE.flib_quit();
 	}
 
-	/**
-	 * Connect to the official Hedgewars server.
-	 * 
-	 * @throws IOException if the metascheme file can't be read or the connection to the server fails
-	 */
-	public void connect(String playerName) throws IOException {
-		connect(playerName, "140.247.62.101", 46631);
-	}
-	
-	/**
-	 * 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 void connect(String playerName, String host, int port) throws IOException {
-		if(playerName == null) {
-			playerName = "Player";
-		}
-		this.playerName = playerName;
-		MetaschemePtr meta = null;
-		try {
-			String metaschemePath = new File(Utils.getDataPathFile(this), "metasettings.ini").getAbsolutePath();
-			meta = FRONTLIB.flib_metascheme_from_ini(metaschemePath);
-			if(meta == null) {
-				throw new RuntimeException("Missing metascheme");
-			}
-			netconn = FRONTLIB.flib_netconn_create(playerName, meta, Utils.getDataPathFile(this).getAbsolutePath(), host, port);
-	    	timer = new CountDownTimer(Long.MAX_VALUE, 50) {
-				@Override
-				public void onTick(long millisUntilFinished) {
-					if(netconn != null) {
-						FRONTLIB.flib_netconn_tick(netconn);
-					}
-				}
-				
-				@Override
-				public void onFinish() {
-				}
-			};
-			timer.start();
-		} catch(FileNotFoundException e) {
-			throw new RuntimeException(e);
-		} finally {
-			FRONTLIB.flib_metascheme_release(meta);
-		}
-	}
-	
-	public void disconnect() {
-		if(timer != null) {
-			timer.cancel();
-		}
-		if(netconn != null) {
-			FRONTLIB.flib_netconn_send_quit(netconn, "User quit");
-			FRONTLIB.flib_netconn_destroy(netconn);
-			netconn = null;
-		}
-	}
-	
 	public class NetplayBinder extends Binder {
-		NetplayService getService() {
-            return NetplayService.this;
+		Netconn getNetconn() {
+            return netconn;
         }
 	}
 
-	public String getPlayerName() {
-		return playerName;
+	public boolean isConnected() {
+		return netconn!=null;
 	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java	Tue Jul 17 22:27:16 2012 +0200
@@ -7,15 +7,10 @@
 
 	public final String name;
 	public final long playerId;
-	public final boolean friend;
-	public final boolean ignored;
 	
-	public Player(String name, long playerId, boolean friend, boolean ignored) {
-		super();
+	public Player(String name, long playerId) {
 		this.name = name;
 		this.playerId = playerId;
-		this.friend = friend;
-		this.ignored = ignored;
 	}
 	
 	private static class ByNameComparator implements Comparator<Player> {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java	Tue Jul 17 22:27:16 2012 +0200
@@ -0,0 +1,51 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PlayerList {
+	private List<Player> list = new LinkedList<Player>();
+	private List<Observer> observers = new LinkedList<Observer>();
+	private long nextId = 1;
+	
+	public List<Player> getList() {
+		return Collections.unmodifiableList(list);
+	}
+	
+	public void observePlayerList(Observer plo) {
+		observers.add(plo);
+	}
+	
+	public void unobservePlayerList(Observer plo) {
+		observers.remove(plo);
+	}
+	
+	void addPlayer(String name) {
+		Player p = new Player(name, nextId++);
+		list.add(p);
+		List<Player> unmodifiableList = Collections.unmodifiableList(list);
+		for(Observer o : observers) {
+			o.itemAdded(unmodifiableList, p);
+		}
+	}
+	
+	void removePlayer(String name) {
+		for(Iterator<Player> iter = list.iterator(); iter.hasNext();) {
+			Player p = iter.next();
+			if(name.equals(p.name)) {
+				iter.remove();
+				List<Player> unmodifiableList = Collections.unmodifiableList(list);
+				for(Observer o : observers) {
+					o.itemDeleted(unmodifiableList, p);
+				}
+			}
+		}
+	}
+
+	public static interface Observer {
+		void itemAdded(List<Player> newList, Player added);
+		void itemDeleted(List<Player> newList, Player deleted);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java	Tue Jul 17 22:27:16 2012 +0200
@@ -6,6 +6,7 @@
 import java.util.List;
 
 import org.hedgewars.hedgeroid.R;
+import org.hedgewars.hedgeroid.netplay.PlayerList.Observer;
 
 import android.content.Context;
 import android.view.LayoutInflater;
@@ -14,7 +15,7 @@
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
-public class PlayerListAdapter extends BaseAdapter {
+public class PlayerListAdapter extends BaseAdapter implements Observer {
 	private List<Player> players = new ArrayList<Player>();
 	private Context context;
 	
@@ -38,6 +39,14 @@
 		return true;
 	}
 
+	public void itemAdded(List<Player> newList, Player added) {
+		setPlayerList(newList);
+	}
+	
+	public void itemDeleted(List<Player> newList, Player deleted) {
+		setPlayerList(newList);
+	}
+	
 	public void setPlayerList(Collection<Player> players) {
 		this.players = new ArrayList<Player>(players);
 		Collections.sort(this.players, Player.nameComparator);
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Tue Jul 17 22:27:16 2012 +0200
@@ -5,23 +5,30 @@
 import java.util.Random;
 
 import org.hedgewars.hedgeroid.R;
+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;
+import android.os.IBinder;
 import android.support.v4.app.ListFragment;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 public class PlayerlistFragment extends ListFragment {
-	List<Player> playerList;
-	Random random = new Random();
-
+	private Netconn netconn;
+	private PlayerListAdapter playerListAdapter;
+	
 	@Override
 	public void onCreate(Bundle savedInstanceState) {
 		super.onCreate(savedInstanceState);
-		playerList = new ArrayList<Player>();
-		PlayerListAdapter playerListAdapter = new PlayerListAdapter(getActivity());
-		playerListAdapter.setPlayerList(playerList);
+		getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection,
+	            Context.BIND_AUTO_CREATE);
+		playerListAdapter = new PlayerListAdapter(getActivity());
 		setListAdapter(playerListAdapter);
 	}
 
@@ -30,4 +37,18 @@
 			Bundle savedInstanceState) {
 		return inflater.inflate(R.layout.lobby_players_fragment, container, false);
 	}
+	
+    private ServiceConnection serviceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder binder) {
+        	netconn = ((NetplayBinder) binder).getNetconn();
+        	playerListAdapter.setPlayerList(netconn.playerList.getList());
+        	netconn.playerList.observePlayerList(playerListAdapter);
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+        	// TODO navigate away
+        	netconn.playerList.unobservePlayerList(playerListAdapter);
+        	netconn = null;
+        }
+    };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java	Tue Jul 17 22:27:16 2012 +0200
@@ -0,0 +1,9 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomPtr;
+/*
+public class Room {
+	public final String name, map, scheme, weapons, owner;
+	public final int players, clans;
+	public final boolean inProgress;
+}*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Signal.java	Tue Jul 17 22:27:16 2012 +0200
@@ -0,0 +1,15 @@
+package org.hedgewars.hedgeroid.netplay;
+
+import java.util.List;
+
+public class Signal<CallbackType> {
+	private List<CallbackType> observers; 
+	
+	public void addListener(CallbackType cb) {
+		observers.add(cb);
+	}
+	
+	public void removeListener(CallbackType cb) {
+		observers.remove(cb);
+	}
+}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TestActivity.java	Mon Jul 16 20:16:03 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/TestActivity.java	Tue Jul 17 22:27:16 2012 +0200
@@ -13,12 +13,12 @@
 	@Override
 	protected void onCreate(Bundle arg0) {
 		super.onCreate(arg0);
-		setContentView(R.layout.activity_lobby_paged);
-		ViewPager pager = (ViewPager)findViewById(R.id.pager);
-		pager.setAdapter(new Adapter(getSupportFragmentManager()));
+		setContentView(R.layout.activity_lobby);
+		/*ViewPager pager = (ViewPager)findViewById(R.id.pager);
+		pager.setAdapter(new Adapter(getSupportFragmentManager()));*/
 	}
 	
-	private static class Adapter extends FragmentPagerAdapter {
+	/*private static class Adapter extends FragmentPagerAdapter {
 		public Adapter(FragmentManager mgr) {
 			super(mgr);
 		}
@@ -37,5 +37,5 @@
 			default: throw new IndexOutOfBoundsException();
 			}
 		}
-	}
+	}*/
 }