# HG changeset patch # User Medo # Date 1343293292 -7200 # Node ID 57a50888405278ab301975ac40feb15ad95441af # Parent 5673e95ef6479eeb3bd28761e20168d99bccce02 Hedgeroid: Major overhaul of the network connection code. Now it is threaded and errors are handled properly. diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/AndroidManifest.xml --- a/project_files/Android-build/SDL-android-project/AndroidManifest.xml Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/AndroidManifest.xml Thu Jul 26 11:01:32 2012 +0200 @@ -46,7 +46,6 @@ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> - There\'s been an error when accessing the SDcard. Please check if there is an SDcard present in the device (internal or external) and if the SDcard is not mounted (via usb to your computer for example). Hedgewars for Android will now quit - Downloading hedgewars files... + Downloading hedgewars files… Successfully downloaded: @@ -22,7 +22,7 @@ Back to main menu Try again The download has failed because of: - Before starting the game we must download some extra files... + Before starting the game we must download some extra files… Are you sure you want to download this package? You\'ve already downloaded this package, are you sure you want to download it again? Download now! @@ -90,7 +90,7 @@ Connect Please select a username. - Enter a username here + Username Info (shown in chat) Follow @@ -105,6 +105,13 @@ An unexpected error has occurred: %1$s The server you tried to connect to is using an incompatible protocol. Unable to authenticate for your username. - The connection to the server was lost. + The connection to the server was lost: %1$s + + + Please wait + Connecting to the server… + Password required + The server has requested a password to connect as "%1$s". + Password diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java Thu Jul 26 11:01:32 2012 +0200 @@ -25,7 +25,8 @@ 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 org.hedgewars.hedgeroid.netplay.Netplay; +import org.hedgewars.hedgeroid.netplay.Netplay.State; import android.app.AlertDialog; import android.app.Dialog; @@ -33,6 +34,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -40,7 +42,8 @@ import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; +import android.text.InputType; +import android.text.method.PasswordTransformationMethod; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -50,16 +53,25 @@ public class MainActivity extends FragmentActivity { private static final int DIALOG_NO_SDCARD = 0; private static final int DIALOG_START_NETGAME = 1; + private static final int DIALOG_CONNECTING = 2; + private static final int DIALOG_PASSWORD = 3; private static final String PREF_PLAYERNAME = "playerName"; + private LocalBroadcastManager broadcastManager; + private Button downloader, startGame; private ProgressDialog assetsDialog; + private String passwordedUsername; // TODO ugly - move dialogs to fragments to get rid of e.g. this public void onCreate(Bundle sis){ super.onCreate(sis); + if(sis != null) { + passwordedUsername = sis.getString(PREF_PLAYERNAME); + } setContentView(R.layout.main); + broadcastManager = LocalBroadcastManager.getInstance(getApplicationContext()); downloader = (Button)findViewById(R.id.downloader); startGame = (Button)findViewById(R.id.startGame); Button joinLobby = (Button)findViewById(R.id.joinLobby); @@ -68,10 +80,17 @@ startGame.setOnClickListener(startGameClicker); joinLobby.setOnClickListener(new OnClickListener() { public void onClick(View v) { - if(!NetplayService.isActive()) { + State state = Netplay.getAppInstance(getApplicationContext()).getState(); + switch(state) { + case NOT_CONNECTED: showDialog(DIALOG_START_NETGAME); - } else { + break; + case CONNECTING: + startWaitingForConnection(); + break; + default: startActivity(new Intent(getApplicationContext(), LobbyActivity.class)); + break; } } }); @@ -100,12 +119,44 @@ } } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(PREF_PLAYERNAME, passwordedUsername); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopWaitingForConnection(); + } + + @Override + protected void onStart() { + super.onStart(); + Netplay.getAppInstance(getApplicationContext()).requestFastTicks(); + } + + @Override + protected void onStop() { + super.onStop(); + Netplay netplay = Netplay.getAppInstance(getApplicationContext()); + netplay.unrequestFastTicks(); + if(netplay.getState() == State.CONNECTING) { + netplay.disconnect(); + } + } + public Dialog onCreateDialog(int id, Bundle args){ switch(id) { case DIALOG_NO_SDCARD: return createNoSdcardDialog(); case DIALOG_START_NETGAME: return createStartNetgameDialog(); + case DIALOG_CONNECTING: + return createConnectingDialog(); + case DIALOG_PASSWORD: + return createPasswordDialog(); default: throw new IndexOutOfBoundsException(); } @@ -150,11 +201,8 @@ edit.putString(PREF_PLAYERNAME, playerName); edit.commit(); - 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)); + startWaitingForConnection(); + Netplay.getAppInstance(getApplicationContext()).connectToDefaultServer(playerName); } } }); @@ -162,6 +210,46 @@ return builder.create(); } + private Dialog createConnectingDialog() { + ProgressDialog dialog = new ProgressDialog(this); + dialog.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + Netplay.getAppInstance(getApplicationContext()).disconnect(); + } + }); + dialog.setIndeterminate(true); + dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + dialog.setTitle(R.string.dialog_connecting_title); + dialog.setMessage(getString(R.string.dialog_connecting_message)); + return dialog; + } + + private Dialog createPasswordDialog() { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + final EditText editText = new EditText(this); + editText.setHint(R.string.dialog_password_hint); + editText.setFreezesText(true); + editText.setId(android.R.id.text1); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); + editText.setTransformationMethod(PasswordTransformationMethod.getInstance()); + builder.setView(editText); + builder.setTitle(R.string.dialog_password_title); + builder.setMessage(getString(R.string.dialog_password_message, passwordedUsername)); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String password = editText.getText().toString(); + editText.setText(""); + Netplay.getAppInstance(getApplicationContext()).sendPassword(password); + } + }); + builder.setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + Netplay.getAppInstance(getApplicationContext()).disconnect(); + } + }); + return builder.create(); + } + public void onAssetsDownloaded(boolean result){ if(!result){ Toast.makeText(this, R.string.download_failed, Toast.LENGTH_LONG).show(); @@ -181,10 +269,44 @@ } }; + private void startWaitingForConnection() { + broadcastManager.registerReceiver(connectedReceiver, new IntentFilter(Netplay.ACTION_CONNECTED)); + broadcastManager.registerReceiver(connectionFailedReceiver, new IntentFilter(Netplay.ACTION_DISCONNECTED)); + broadcastManager.registerReceiver(passwordRequestedReceiver, new IntentFilter(Netplay.ACTION_PASSWORD_REQUESTED)); + showDialog(DIALOG_CONNECTING); + } + + private void stopWaitingForConnection() { + broadcastManager.unregisterReceiver(connectedReceiver); + broadcastManager.unregisterReceiver(connectionFailedReceiver); + broadcastManager.unregisterReceiver(passwordRequestedReceiver); + } + private BroadcastReceiver connectedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + stopWaitingForConnection(); + dismissDialog(DIALOG_CONNECTING); startActivity(new Intent(getApplicationContext(), LobbyActivity.class)); } }; + + private BroadcastReceiver connectionFailedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + stopWaitingForConnection(); + dismissDialog(DIALOG_CONNECTING); + if(intent.getBooleanExtra(Netplay.EXTRA_HAS_ERROR, true)) { + Toast.makeText(getApplicationContext(), intent.getStringExtra(Netplay.EXTRA_MESSAGE), Toast.LENGTH_LONG).show(); + } + } + }; + + private BroadcastReceiver passwordRequestedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + passwordedUsername = intent.getStringExtra(Netplay.EXTRA_PLAYERNAME); + showDialog(DIALOG_PASSWORD); + } + }; } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/JnaFrontlib.java Thu Jul 26 11:01:32 2012 +0200 @@ -82,17 +82,17 @@ /** * Returns the (native-owned) rooms in this list */ - public RoomPtr[] getRooms(int count) { + public Room[] getRooms(int count) { Pointer ptr = getPointer(); if(ptr == null) { - return new RoomPtr[0]; + return new Room[0]; } Pointer[] untypedPtrs = ptr.getPointerArray(0, count); - RoomPtr[] typedPtrs = new RoomPtr[count]; + Room[] result = new Room[count]; for(int i=0; i0) { - editText.setText(""); - service.sendChat(text); - } - } + private Netplay netconn; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + netconn = Netplay.getAppInstance(getActivity().getApplicationContext()); adapter = new ChatlogAdapter(getActivity()); + adapter.setLog(netconn.lobbyChatlog.getLog()); + netconn.lobbyChatlog.registerObserver(adapter); } @Override public void onStart() { super.onStart(); - getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection, - Context.BIND_AUTO_CREATE); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.lobby_chat_fragment, container, false); - editText = (EditText) view.findViewById(R.id.lobbyChatInput); - listView = (ListView) view.findViewById(R.id.lobbyConsole); + ListView listView = (ListView) view.findViewById(R.id.lobbyConsole); listView.setAdapter(adapter); listView.setDivider(null); listView.setDividerHeight(0); listView.setVerticalFadingEdgeEnabled(true); - editText.setOnEditorActionListener(new OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - boolean handled = false; - if(actionId == EditorInfo.IME_ACTION_SEND) { - commitText(); - handled = true; - } - return handled; - } - }); + EditText editText = (EditText) view.findViewById(R.id.lobbyChatInput); + editText.setOnEditorActionListener(new ChatSendListener()); return view; } @@ -78,21 +53,21 @@ @Override public void onDestroy() { super.onDestroy(); - getActivity().unbindService(serviceConnection); + netconn.lobbyChatlog.unregisterObserver(adapter); } - private ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - Log.d("LobbyChatFragment", "netconn received"); - service = ((NetplayBinder) binder).getService(); - adapter.setLog(service.lobbyChatlog.getLog()); - service.lobbyChatlog.registerObserver(adapter); - } - - public void onServiceDisconnected(ComponentName className) { - // TODO navigate away - service.lobbyChatlog.unregisterObserver(adapter); - service = null; - } - }; + private final class ChatSendListener implements OnEditorActionListener { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + boolean handled = false; + if(actionId == EditorInfo.IME_ACTION_SEND) { + String text = v.getText().toString(); + if(text.length()>0) { + v.setText(""); + netconn.sendChat(text); + handled = true; + } + } + return handled; + } + } } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netplay.java Thu Jul 26 11:01:32 2012 +0200 @@ -0,0 +1,527 @@ +package org.hedgewars.hedgeroid.netplay; + +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 android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.util.Pair; + +import com.sun.jna.Pointer; + +/** + * This class manages the application's networking state. + */ +public class Netplay { + public static enum State { NOT_CONNECTED, CONNECTING, LOBBY, ROOM, INGAME } + + // Extras in broadcasts + public static final String EXTRA_PLAYERNAME = "playerName"; + 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"; + public static final String ACTION_PASSWORD_REQUESTED = ACTIONPREFIX+"PASSWORD_REQUESTED"; + + public static final String DEFAULT_SERVER = "netserver.hedgewars.org"; + public static final int DEFAULT_PORT = 46631; + + private final Context appContext; + private final LocalBroadcastManager broadcastManager; + private final FromNetHandler fromNetHandler = new FromNetHandler(); + + private State state; + private int foregroundUsers = 0; + + // null if there is no running connection (==state is NOT_CONNECTED) + private ThreadedNetConnection connection; + + public final PlayerList playerList = new PlayerList(); + public final RoomList roomList = new RoomList(); + public final MessageLog lobbyChatlog; + public final MessageLog roomChatlog; + + public Netplay(Context appContext) { + this.appContext = appContext; + broadcastManager = LocalBroadcastManager.getInstance(appContext); + lobbyChatlog = new MessageLog(appContext); + roomChatlog = new MessageLog(appContext); + state = State.NOT_CONNECTED; + } + + private void clearState() { + playerList.clear(); + roomList.clear(); + lobbyChatlog.clear(); + roomChatlog.clear(); + } + + public void connectToDefaultServer(String playerName) { + connect(playerName, DEFAULT_SERVER, DEFAULT_PORT); + } + + /** + * Establish a new connection. Only call if the current state is NOT_CONNECTED. + * + * The state will switch to CONNECTING immediately. After that, it can asynchronously change to any other state. + * State changes are indicated by broadcasts. In particular, if an error occurs while trying to connect, the state + * will change back to NOT_CONNECTED and an ACTION_DISCONNECTED broadcast is sent. + */ + public void connect(String name, String host, int port) { + if(state != State.NOT_CONNECTED) { + throw new IllegalStateException("Attempt to start a new connection while the old one was still running."); + } + + clearState(); + state = State.CONNECTING; + connection = ThreadedNetConnection.startConnection(appContext, fromNetHandler, name, host, port); + connection.setFastTickRate(foregroundUsers > 0); + } + + public void sendNick(String nick) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_NICK, nick); } + public void sendPassword(String password) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PASSWORD, password); } + public void sendQuit(String message) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_QUIT, message); } + public void sendRoomlistRequest() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_ROOMLIST_REQUEST); } + public void sendPlayerInfoQuery(String name) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_PLAYER_INFO_REQUEST, name); } + public void sendChat(final String s) { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_SEND_CHAT, s); } + public void disconnect() { sendToNet(ThreadedNetConnection.ToNetHandler.MSG_DISCONNECT, "User Quit"); } + + private static Netplay instance; + + /** + * Retrieve the single app-wide instance of the netplay interface, creating it if it + * does not exist yet. + * + * @param applicationContext + * @return + */ + public static Netplay getAppInstance(Context applicationContext) { + if(instance == null) { + // We'll just do it here and never quit it again... + if(Flib.INSTANCE.flib_init() != 0) { + throw new RuntimeException("Unable to start frontlib"); + } + instance = new Netplay(applicationContext); + } + return instance; + } + + public State getState() { + return state; + } + + /** + * Indicate that you want network messages to be checked regularly (several times per second). + * As long as nobody requests fast ticks, the network is only checked once every few seconds + * to conserve battery power. + * Once you no longer need fast updates, call unrequestFastTicks. + */ + public void requestFastTicks() { + if(foregroundUsers == Integer.MAX_VALUE) { + throw new RuntimeException("Reference counter overflow"); + } + if(foregroundUsers == 0 && connection != null) { + connection.setFastTickRate(true); + } + foregroundUsers++; + } + + public void unrequestFastTicks() { + if(foregroundUsers == 0) { + throw new RuntimeException("Reference counter underflow"); + } + foregroundUsers--; + if(foregroundUsers == 0 && connection != null) { + connection.setFastTickRate(false); + } + } + + private boolean sendToNet(int what) { + if(connection != null) { + Handler handler = connection.toNetHandler; + return handler.sendMessage(handler.obtainMessage(what)); + } else { + return false; + } + } + + private boolean sendToNet(int what, Object obj) { + if(connection != null) { + Handler handler = connection.toNetHandler; + return handler.sendMessage(handler.obtainMessage(what, obj)); + } else { + return false; + } + } + + private MessageLog getCurrentLog() { + if(state == State.ROOM || state == State.INGAME) { + return roomChatlog; + } else { + return lobbyChatlog; + } + } + + /** + * Processes messages from the networking system. Always runs on the main thread. + */ + final class FromNetHandler extends Handler { + public static final int MSG_LOBBY_JOIN = 0; + public static final int MSG_LOBBY_LEAVE = 1; + public static final int MSG_CHAT = 2; + public static final int MSG_MESSAGE = 3; + public static final int MSG_ROOM_ADD = 4; + public static final int MSG_ROOM_UPDATE = 5; + public static final int MSG_ROOM_DELETE = 6; + public static final int MSG_ROOMLIST = 7; + public static final int MSG_CONNECTED = 8; + public static final int MSG_DISCONNECTED = 9; + public static final int MSG_PASSWORD_REQUEST = 10; + + public FromNetHandler() { + super(Looper.getMainLooper()); + } + + @SuppressWarnings("unchecked") + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_LOBBY_JOIN: { + String name = (String)msg.obj; + playerList.addPlayerWithNewId(name); + lobbyChatlog.appendPlayerJoin(name); + break; + } + case MSG_LOBBY_LEAVE: { + Pair args = (Pair)msg.obj; + playerList.removePlayer(args.first); + lobbyChatlog.appendPlayerLeave(args.first, args.second); + break; + } + case MSG_CHAT: { + Pair args = (Pair)msg.obj; + getCurrentLog().appendChat(args.first, args.second); + break; + } + case MSG_MESSAGE: { + getCurrentLog().appendMessage(msg.arg1, (String)msg.obj); + break; + } + case MSG_ROOM_ADD: { + roomList.addRoomWithNewId((Room)msg.obj); + break; + } + case MSG_ROOM_UPDATE: { + Pair args = (Pair)msg.obj; + roomList.updateRoom(args.first, args.second); + break; + } + case MSG_ROOM_DELETE: { + roomList.removeRoom((String)msg.obj); + break; + } + case MSG_ROOMLIST: { + roomList.updateList((Room[])msg.obj); + break; + } + case MSG_CONNECTED: { + state = State.LOBBY; + broadcastManager.sendBroadcast(new Intent(ACTION_CONNECTED)); + break; + } + case MSG_DISCONNECTED: { + Pair args = (Pair)msg.obj; + state = State.NOT_CONNECTED; + connection = null; + Intent intent = new Intent(ACTION_DISCONNECTED); + intent.putExtra(EXTRA_HAS_ERROR, args.first); + intent.putExtra(EXTRA_MESSAGE, args.second); + broadcastManager.sendBroadcastSync(intent); + break; + } + case MSG_PASSWORD_REQUEST: { + Intent intent = new Intent(ACTION_PASSWORD_REQUESTED); + intent.putExtra(EXTRA_PLAYERNAME, (String)msg.obj); + broadcastManager.sendBroadcast(intent); + break; + } + default: { + Log.e("FromNetHandler", "Unknown message type: "+msg.what); + break; + } + } + } + } + + /** + * This class handles the actual communication with the networking library, on a separate thread. + */ + private static class ThreadedNetConnection { + private static final long TICK_INTERVAL_FAST = 100; + private static final long TICK_INTERVAL_SLOW = 5000; + private static final JnaFrontlib FLIB = Flib.INSTANCE; + + public final ToNetHandler toNetHandler; + + private final Context appContext; + private final FromNetHandler fromNetHandler; + private final TickHandler tickHandler; + + /** + * conn can only be null while connecting (the first thing in the thread), and directly after disconnecting, + * in the same message (the looper is shut down on disconnect, so there will be no messages after that). + */ + private NetconnPtr conn; + private String playerName; + + private ThreadedNetConnection(Context appContext, FromNetHandler fromNetHandler) { + this.appContext = appContext; + this.fromNetHandler = fromNetHandler; + + HandlerThread thread = new HandlerThread("NetThread"); + thread.start(); + toNetHandler = new ToNetHandler(thread.getLooper()); + tickHandler = new TickHandler(thread.getLooper(), TICK_INTERVAL_FAST, tickCb); + } + + private void connect(final String name, final String host, final int port) { + toNetHandler.post(new Runnable() { + public void run() { + playerName = name == null ? "Player" : name; + MetaschemePtr meta = null; + File dataPath; + try { + dataPath = Utils.getDataPathFile(appContext); + } catch (FileNotFoundException e) { + shutdown(true, appContext.getString(R.string.sdcard_not_mounted)); + return; + } + String metaschemePath = new File(dataPath, "metasettings.ini").getAbsolutePath(); + meta = FLIB.flib_metascheme_from_ini(metaschemePath); + if(meta == null) { + shutdown(true, appContext.getString(R.string.error_unexpected, "Missing metasettings.ini")); + return; + } + conn = FLIB.flib_netconn_create(playerName, meta, dataPath.getAbsolutePath(), host, port); + if(conn == null) { + shutdown(true, appContext.getString(R.string.error_connection_failed)); + return; + } + 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_netconn_onPasswordRequest(conn, passwordRequestCb, null); + FLIB.flib_metascheme_release(meta); + tickHandler.start(); + } + }); + } + + public static ThreadedNetConnection startConnection(Context appContext, FromNetHandler fromNetHandler, String playerName, String host, int port) { + ThreadedNetConnection result = new ThreadedNetConnection(appContext, fromNetHandler); + result.connect(playerName, host, port); + return result; + } + + public void setFastTickRate(boolean fastTickRate) { + tickHandler.setInterval(fastTickRate ? TICK_INTERVAL_FAST : TICK_INTERVAL_SLOW); + } + + private final Runnable tickCb = new Runnable() { + public void run() { + FLIB.flib_netconn_tick(conn); + } + }; + + private final StrCallback lobbyJoinCb = new StrCallback() { + public void callback(Pointer context, String name) { + sendFromNet(FromNetHandler.MSG_LOBBY_JOIN, name); + } + }; + + private final StrStrCallback lobbyLeaveCb = new StrStrCallback() { + public void callback(Pointer context, String name, String msg) { + sendFromNet(FromNetHandler.MSG_LOBBY_LEAVE, Pair.create(name, msg)); + } + }; + + private final StrStrCallback chatCb = new StrStrCallback() { + public void callback(Pointer context, String name, String msg) { + sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(name, msg)); + } + }; + + private final IntStrCallback messageCb = new IntStrCallback() { + public void callback(Pointer context, int type, String msg) { + sendFromNet(FromNetHandler.MSG_MESSAGE, type, msg); + } + }; + + private final RoomCallback roomAddCb = new RoomCallback() { + public void callback(Pointer context, RoomPtr roomPtr) { + sendFromNet(FromNetHandler.MSG_ROOM_ADD, roomPtr.deref()); + } + }; + + private final StrRoomCallback roomUpdateCb = new StrRoomCallback() { + public void callback(Pointer context, String name, RoomPtr roomPtr) { + sendFromNet(FromNetHandler.MSG_ROOM_UPDATE, Pair.create(name, roomPtr.deref())); + } + }; + + private final StrCallback roomDeleteCb = new StrCallback() { + public void callback(Pointer context, final String name) { + sendFromNet(FromNetHandler.MSG_ROOM_DELETE, name); + } + }; + + private final RoomListCallback roomlistCb = new RoomListCallback() { + public void callback(Pointer context, RoomArrayPtr arg1, int count) { + sendFromNet(FromNetHandler.MSG_ROOMLIST, arg1.getRooms(count)); + } + }; + + private final VoidCallback connectedCb = new VoidCallback() { + public void callback(Pointer context) { + FLIB.flib_netconn_send_request_roomlist(conn); + playerName = FLIB.flib_netconn_get_playername(conn); + sendFromNet(FromNetHandler.MSG_CONNECTED, playerName); + } + }; + + private final StrCallback passwordRequestCb = new StrCallback() { + public void callback(Pointer context, String nickname) { + sendFromNet(FromNetHandler.MSG_PASSWORD_REQUEST, playerName); + } + }; + + private void shutdown(boolean error, String message) { + if(conn != null) { + FLIB.flib_netconn_destroy(conn); + conn = null; + } + tickHandler.stop(); + toNetHandler.getLooper().quit(); + sendFromNet(FromNetHandler.MSG_DISCONNECTED, Pair.create(error, message)); + } + + private final IntStrCallback disconnectCb = new IntStrCallback() { + public void callback(Pointer context, int reason, String message) { + Boolean error = reason != JnaFrontlib.NETCONN_DISCONNECT_NORMAL; + String messageForUser = createDisconnectUserMessage(appContext.getResources(), reason, message); + shutdown(error, messageForUser); + } + }; + + private static String createDisconnectUserMessage(Resources res, int reason, String message) { + switch(reason) { + case JnaFrontlib.NETCONN_DISCONNECT_AUTH_FAILED: + return res.getString(R.string.error_auth_failed); + case JnaFrontlib.NETCONN_DISCONNECT_CONNLOST: + return res.getString(R.string.error_connection_lost, message); + case JnaFrontlib.NETCONN_DISCONNECT_INTERNAL_ERROR: + return res.getString(R.string.error_unexpected, message); + case JnaFrontlib.NETCONN_DISCONNECT_SERVER_TOO_OLD: + return res.getString(R.string.error_server_too_old); + default: + return message; + } + } + + private boolean sendFromNet(int what, Object obj) { + return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, obj)); + } + + private boolean sendFromNet(int what, int arg1, Object obj) { + return fromNetHandler.sendMessage(fromNetHandler.obtainMessage(what, arg1, 0, obj)); + } + + /** + * Processes messages to the networking system. Runs on a non-main thread. + */ + public final class ToNetHandler extends Handler { + public static final int MSG_SEND_NICK = 0; + public static final int MSG_SEND_PASSWORD = 1; + public static final int MSG_SEND_QUIT = 2; + public static final int MSG_SEND_ROOMLIST_REQUEST = 3; + public static final int MSG_SEND_PLAYER_INFO_REQUEST = 4; + public static final int MSG_SEND_CHAT = 5; + public static final int MSG_DISCONNECT = 6; + + public ToNetHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_SEND_NICK: { + FLIB.flib_netconn_send_nick(conn, (String)msg.obj); + break; + } + case MSG_SEND_PASSWORD: { + FLIB.flib_netconn_send_password(conn, (String)msg.obj); + break; + } + case MSG_SEND_QUIT: { + FLIB.flib_netconn_send_quit(conn, (String)msg.obj); + break; + } + case MSG_SEND_ROOMLIST_REQUEST: { + FLIB.flib_netconn_send_request_roomlist(conn); // TODO restrict to lobby state? + break; + } + case MSG_SEND_PLAYER_INFO_REQUEST: { + FLIB.flib_netconn_send_playerInfo(conn, (String)msg.obj); + break; + } + case MSG_SEND_CHAT: { + if(FLIB.flib_netconn_send_chat(conn, (String)msg.obj) == 0) { + sendFromNet(FromNetHandler.MSG_CHAT, Pair.create(playerName, (String)msg.obj)); + } + break; + } + case MSG_DISCONNECT: { + FLIB.flib_netconn_send_quit(conn, (String)msg.obj); + shutdown(false, "User quit"); + break; + } + default: { + Log.e("ToNetHandler", "Unknown message type: "+msg.what); + break; + } + } + } + } + } +} diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/NetplayService.java Tue Jul 24 16:57:48 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,317 +0,0 @@ -package org.hedgewars.hedgeroid.netplay; - -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.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(); - 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", "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); - } - } - }); - if(Flib.INSTANCE.flib_init() != 0) { - stopWithError(getString(R.string.error_unexpected, "Unable to start frontlib")); - } - } - - @Override - public void onDestroy() { - 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 { - 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 static boolean isActive() { - return instance!=null; - } -} - diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java Thu Jul 26 11:01:32 2012 +0200 @@ -1,21 +1,9 @@ package org.hedgewars.hedgeroid.netplay; -import java.util.Comparator; - public class Player { - public static final ByNameComparator NAME_COMPARATOR = new ByNameComparator(); - public final String name; - public final long id; // for ListView - public Player(String name, long id) { + public Player(String name) { this.name = name; - this.id = id; - } - - private static final class ByNameComparator implements Comparator { - public int compare(Player lhs, Player rhs) { - return lhs.name.compareToIgnoreCase(rhs.name); - } } } \ No newline at end of file diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java Thu Jul 26 11:01:32 2012 +0200 @@ -5,14 +5,14 @@ import java.util.TreeMap; import android.database.DataSetObservable; +import android.util.Pair; public class PlayerList extends DataSetObservable { private long nextId = 1; - private Map players = new TreeMap(); + private Map> players = new TreeMap>(); public void addPlayerWithNewId(String name) { - Player p = new Player(name, nextId++); - players.put(name, p); + players.put(name, Pair.create(new Player(name), nextId++)); notifyChanged(); } @@ -29,7 +29,7 @@ } } - public Map getMap() { + public Map> getMap() { return Collections.unmodifiableMap(players); } } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java Thu Jul 26 11:01:32 2012 +0200 @@ -2,12 +2,14 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import org.hedgewars.hedgeroid.R; import android.content.Context; import android.database.DataSetObserver; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,7 +17,7 @@ import android.widget.TextView; public class PlayerListAdapter extends BaseAdapter { - private List players = new ArrayList(); + private List> players = new ArrayList>(); private Context context; private PlayerList playerList; @@ -40,11 +42,11 @@ } public Player getItem(int position) { - return players.get(position); + return players.get(position).first; } public long getItemId(int position) { - return players.get(position).id; + return players.get(position).second; } public boolean hasStableIds() { @@ -61,7 +63,6 @@ } public void invalidate() { - players = new ArrayList(); if(playerList != null) { playerList.unregisterObserver(observer); } @@ -70,8 +71,8 @@ } private void reloadFromList(PlayerList list) { - players = new ArrayList(list.getMap().values()); - Collections.sort(players, Player.NAME_COMPARATOR); + players = new ArrayList>(list.getMap().values()); + Collections.sort(players, AlphabeticalOrderComparator.INSTANCE); notifyDataSetChanged(); } @@ -85,9 +86,16 @@ tv.setCompoundDrawablesWithIntrinsicBounds(R.drawable.human, 0, 0, 0); } - String player = players.get(position).name; + String player = players.get(position).first.name; TextView username = (TextView) v.findViewById(android.R.id.text1); username.setText(player); return v; } + + private static final class AlphabeticalOrderComparator implements Comparator> { + public static final AlphabeticalOrderComparator INSTANCE = new AlphabeticalOrderComparator(); + public int compare(Pair lhs, Pair rhs) { + return lhs.first.name.compareToIgnoreCase(rhs.first.name); + }; + } } \ No newline at end of file diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java Thu Jul 26 11:01:32 2012 +0200 @@ -1,37 +1,39 @@ package org.hedgewars.hedgeroid.netplay; import org.hedgewars.hedgeroid.R; -import org.hedgewars.hedgeroid.netplay.NetplayService.NetplayBinder; -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; import android.os.Bundle; -import android.os.IBinder; import android.support.v4.app.ListFragment; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.ContextMenu.ContextMenuInfo; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Toast; -import android.widget.AdapterView.AdapterContextMenuInfo; public class PlayerlistFragment extends ListFragment { - private NetplayService netplayService; + private Netplay netconn; private PlayerListAdapter playerListAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection, 0); + netconn = Netplay.getAppInstance(getActivity().getApplicationContext()); playerListAdapter = new PlayerListAdapter(getActivity()); + playerListAdapter.setList(Netplay.getAppInstance(getActivity().getApplicationContext()).playerList); setListAdapter(playerListAdapter); } @Override + public void onDestroy() { + super.onDestroy(); + playerListAdapter.invalidate(); + } + + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); registerForContextMenu(getListView()); @@ -41,8 +43,11 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); + AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.lobby_playerlist_context, menu); + menu.setHeaderIcon(R.drawable.human); + menu.setHeaderTitle(playerListAdapter.getItem(info.position).name); } @Override @@ -51,9 +56,7 @@ switch(item.getItemId()) { case R.id.player_info: Player p = playerListAdapter.getItem(info.position); - if(netplayService != null) { - netplayService.sendPlayerInfoQuery(p.name); - } + netconn.sendPlayerInfoQuery(p.name); return true; case R.id.player_follow: Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show(); @@ -64,27 +67,8 @@ } @Override - public void onDestroy() { - super.onDestroy(); - getActivity().unbindService(serviceConnection); - } - - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.lobby_players_fragment, container, false); } - - private ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - netplayService = ((NetplayBinder) binder).getService(); - playerListAdapter.setList(netplayService.playerList); - } - - public void onServiceDisconnected(ComponentName className) { - // TODO navigate away - playerListAdapter.invalidate(); - netplayService = null; - } - }; } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java Thu Jul 26 11:01:32 2012 +0200 @@ -1,7 +1,5 @@ package org.hedgewars.hedgeroid.netplay; -import java.util.Comparator; - import org.hedgewars.hedgeroid.R; import android.content.res.Resources; @@ -10,15 +8,13 @@ public static final String MAP_REGULAR = "+rnd+"; public static final String MAP_MAZE = "+maze+"; public static final String MAP_DRAWN = "+drawn+"; - public static final Comparator ID_COMPARATOR = new ByIdComparator(); public final String name, map, scheme, weapons, owner; public final int playerCount, teamCount; public final boolean inProgress; - public final long id; // for ListView public Room(String name, String map, String scheme, String weapons, - String owner, int playerCount, int teamCount, boolean inProgress, long id) { + String owner, int playerCount, int teamCount, boolean inProgress) { this.name = name; this.map = map; this.scheme = scheme; @@ -27,7 +23,6 @@ this.playerCount = playerCount; this.teamCount = teamCount; this.inProgress = inProgress; - this.id = id; } public static String formatMapName(Resources res, String map) { @@ -42,10 +37,4 @@ } return map; } - - private static final class ByIdComparator implements Comparator { - public int compare(Room lhs, Room rhs) { - return lhs.idrhs.id ? 1 : 0; - } - } } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java Thu Jul 26 11:01:32 2012 +0200 @@ -4,46 +4,43 @@ import java.util.Map; import java.util.TreeMap; -import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomPtr; - import android.database.DataSetObservable; import android.util.Log; +import android.util.Pair; public class RoomList extends DataSetObservable { private long nextId = 1; - private Map rooms = new TreeMap(); + private Map> rooms = new TreeMap>(); - public void updateList(RoomPtr[] roomPtrs) { - Map newMap = new TreeMap(); - for(RoomPtr roomPtr : roomPtrs) { - JnaFrontlib.Room room = roomPtr.deref(); - Room oldEntry = rooms.get(room.name); + public void updateList(Room[] newRooms) { + Map> newMap = new TreeMap>(); + for(Room room : newRooms) { + Pair oldEntry = rooms.get(room.name); if(oldEntry == null) { - newMap.put(room.name, buildRoom(room, nextId++)); + newMap.put(room.name, Pair.create(room, nextId++)); } else { - newMap.put(room.name, buildRoom(room, oldEntry.id)); + newMap.put(room.name, Pair.create(room, oldEntry.second)); } } rooms = newMap; notifyChanged(); } - public void addRoomWithNewId(RoomPtr roomPtr) { - putRoom(roomPtr.deref(), nextId++); + public void addRoomWithNewId(Room room) { + rooms.put(room.name, Pair.create(room, nextId++)); notifyChanged(); } - public void updateRoom(String name, RoomPtr roomPtr) { - JnaFrontlib.Room room = roomPtr.deref(); - Room oldEntry = rooms.get(name); + public void updateRoom(String name, Room room) { + Pair oldEntry = rooms.get(name); if(oldEntry == null) { Log.e("RoomList", "Received update for unknown room: "+name); - putRoom(room, nextId++); + rooms.put(room.name, Pair.create(room, nextId++)); } else { if(!name.equals(room.name)) { rooms.remove(name); } - putRoom(room, oldEntry.id); + rooms.put(room.name, Pair.create(room, oldEntry.second)); } notifyChanged(); } @@ -61,15 +58,7 @@ } } - public Map getMap() { + public Map> getMap() { return Collections.unmodifiableMap(rooms); } - - private void putRoom(JnaFrontlib.Room r, long id) { - rooms.put(r.name, buildRoom(r, id)); - } - - private Room buildRoom(JnaFrontlib.Room r, long id) { - return new Room(r.name, r.map, r.scheme, r.weapons, r.owner, r.playerCount, r.teamCount, r.inProgress, id); - } } diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java Thu Jul 26 11:01:32 2012 +0200 @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import org.hedgewars.hedgeroid.R; @@ -9,6 +10,7 @@ import android.content.Context; import android.content.res.Resources; import android.database.DataSetObserver; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -16,7 +18,7 @@ import android.widget.TextView; public class RoomListAdapter extends BaseAdapter { - private List rooms = new ArrayList(); + private List> rooms = new ArrayList>(); private Context context; private RoomList roomList; @@ -41,11 +43,11 @@ } public Room getItem(int position) { - return rooms.get(position); + return rooms.get(position).first; } public long getItemId(int position) { - return rooms.get(position).id; + return rooms.get(position).second; } public boolean hasStableIds() { @@ -62,7 +64,6 @@ } public void invalidate() { - rooms = new ArrayList(); if(roomList != null) { roomList.unregisterObserver(observer); } @@ -71,8 +72,8 @@ } private void reloadFromList(RoomList list) { - rooms = new ArrayList(roomList.getMap().values()); - Collections.sort(rooms, Collections.reverseOrder(Room.ID_COMPARATOR)); + rooms = new ArrayList>(roomList.getMap().values()); + Collections.sort(rooms, RoomAgeComparator.INSTANCE); notifyDataSetChanged(); } @@ -91,7 +92,7 @@ v = vi.inflate(R.layout.listview_room, null); } - Room room = rooms.get(position); + Room room = rooms.get(position).first; int iconRes = room.inProgress ? R.drawable.roomlist_ingame : R.drawable.roomlist_preparing; if(v.findViewById(android.R.id.text1) == null) { @@ -128,4 +129,11 @@ return v; } + + private static final class RoomAgeComparator implements Comparator> { + public static final RoomAgeComparator INSTANCE = new RoomAgeComparator(); + public int compare(Pair lhs, Pair rhs) { + return rhs.second.compareTo(lhs.second); + } + } } \ No newline at end of file diff -r 5673e95ef647 -r 57a508884052 project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java --- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java Tue Jul 24 16:57:48 2012 +0200 +++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java Thu Jul 26 11:01:32 2012 +0200 @@ -1,15 +1,9 @@ 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.os.Bundle; import android.os.CountDownTimer; -import android.os.IBinder; import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.Menu; @@ -17,21 +11,19 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView; import android.widget.Toast; public class RoomlistFragment extends ListFragment implements OnItemClickListener { private static final int AUTO_REFRESH_INTERVAL_MS = 15000; - private NetplayService service; + private Netplay netconn; private RoomListAdapter adapter; private CountDownTimer autoRefreshTimer = new CountDownTimer(Long.MAX_VALUE, AUTO_REFRESH_INTERVAL_MS) { @Override public void onTick(long millisUntilFinished) { - if(service != null && service.isConnected()) { - service.sendRoomlistRequest(); - } + netconn.sendRoomlistRequest(); } @Override @@ -41,9 +33,9 @@ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getActivity().bindService(new Intent(getActivity(), NetplayService.class), serviceConnection, - Context.BIND_AUTO_CREATE); + netconn = Netplay.getAppInstance(getActivity().getApplicationContext()); adapter = new RoomListAdapter(getActivity()); + adapter.setList(netconn.roomList); setListAdapter(adapter); setHasOptionsMenu(true); } @@ -51,8 +43,7 @@ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.lobby_rooms_fragment, container, false); - return v; + return inflater.inflate(R.layout.lobby_rooms_fragment, container, false); } @Override @@ -64,10 +55,7 @@ @Override public void onResume() { super.onResume(); - if(service != null) { - service.sendRoomlistRequest(); - autoRefreshTimer.start(); - } + autoRefreshTimer.start(); } @Override @@ -79,7 +67,7 @@ @Override public void onDestroy() { super.onDestroy(); - getActivity().unbindService(serviceConnection); + adapter.invalidate(); } @Override @@ -92,9 +80,7 @@ public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.roomlist_refresh: - if(service != null && service.isConnected()) { - service.sendRoomlistRequest(); - } + netconn.sendRoomlistRequest(); return true; default: return super.onOptionsItemSelected(item); @@ -104,18 +90,4 @@ public void onItemClick(AdapterView parent, View view, int position, long id) { Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show(); } - - private ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - service = ((NetplayBinder) binder).getService(); - adapter.setList(service.roomList); - autoRefreshTimer.start(); - } - - public void onServiceDisconnected(ComponentName className) { - // TODO navigate away - adapter.invalidate(); - service = null; - } - }; }