<uses-permission android:name="android.permission.INTERNET" />
<application android:label="@string/app_name" >
<activity android:name="Main"
- android:label="@string/app_name">
+ android:windowSoftInputMode="stateVisible|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <service android:name="Task">
+ </service>
</application>
</manifest>
--- /dev/null
+/* Android Glue */
+Main: extends Activity // UI Code:
+ Handler handler; // Commands sent to Main by UI
+ Messenger messenger; // though handler/messenger
+
+ bool onStart(); ... // Activity methods
+
+ void onButtonClick(); ... // Widget callbacks
+
+ void onRegister(task); // Set Task object on startup
+ void onConnect(void); // IRC connected
+ void onDisconnect(void); // IRC disconnected
+ void onMessage(msg); // IRC message received
+
+Task: extends Service // IRC connection as service:
+ bool onCreate(); ... // Service methods
+
+ void run(); // IRC Client Thread entry
+
+ Message send(String txt); // Send IRC message
+
+Util: // Misc functions
+ void debug(String str); // Print to debug log
+
+/* IRC Client Protocol */
+Client: // IRC Client:
+ void connect(srv, nick, chan); // Connect to server
+
+ Message send(String msg) // Send text as message
+ Message recv(void) // Blocking recv message
+
+Message: // IRC Message:
+
+/* Spades game */
+Spades:
+ Valid setHand(Set<Card> hand);
+ Valid setPile(Set<Card> pile);
+ Valid canPlay(Person who, Card card);
+
+/* OpenGL Graphics */
+Cards:
+ void addBidListener(BidListener listener);
+ void addPassListener(PassListener listener);
+ void addLookListener(LookListener listener);
+ void addPlayListener(PlayListener listener);
+ void addTurnListener(TurnListener listener);
+
+ void bid(Person who, int bid);
+ void pass(Card card);
+ void show(Set<Card> hand);
+ void play(Person who, Card card);
+
+/* Listeners */
+void MsgListener(Message msg);
+
+Valid BidListener(int bid);
+Valid PassListener(Card card);
+Valid LookListener(void);
+Valid PlayListener(Card card);
+Valid TurnListener(void);
+
+/* Types */
+Card:
+
+Team:
+ - Player a;
+ - Player b;
+
+Person:
+ - nickname
+
+// vim: ft=java
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
+ android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
- <TextView
- android:id="@+id/textview"
+ <LinearLayout
+ android:orientation="vertical"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="hello, spades" />
-</LinearLayout>
-
+ android:layout_height="fill_parent">
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <LinearLayout
+ android:id="@+id/chat"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <ScrollView
+ android:id="@+id/log_scroll"
+ android:layout_width="fill_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:scrollbars="vertical"
+ android:fillViewport="true">
+ <TextView
+ android:id="@+id/log"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+ </ScrollView>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <EditText
+ android:id="@+id/input"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_weight="1" />
+ <Button
+ android:id="@+id/send"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:onClick="onSend"
+ android:text="Send" />
+ </LinearLayout>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/spades"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:text="Spades" />
+ <ScrollView
+ android:id="@+id/debug_scroll"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"
+ android:fillViewport="true">
+ <TextView
+ android:id="@+id/debug"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+ </ScrollView>
+ </FrameLayout>
+ </LinearLayout>
+</TabHost>
+<!-- vim: set ts=4 sw=4 sts=4: -->
--- /dev/null
+package org.pileus.spades;
+
+public class Cards
+{
+ /* Public Methods */
+ public Cards()
+ {
+ Os.debug("Cards create");
+ }
+}
import java.io.BufferedReader;
import java.io.PrintWriter;
-import android.util.Log;
-
public class Client
{
/* Private data */
/* Public data */
public boolean running = true;
- /* Private methods */
- private void process(Message msg)
- {
- if (msg.cmd.equals("001") && msg.msg.matches("Welcome.*")) {
- putline("JOIN " + channel);
- putline("TOPIC " + channel);
- }
- if (msg.cmd.equals("PING")) {
- putline("PING " + msg.msg);
- }
- }
-
- private String getline()
- {
- try {
- String line = input.readLine();
- Log.d("Spades", "> " + line);
- return line;
- } catch (Exception e) {
- Log.d("Spades", "Error reading line", e);
- this.running = false;
- return "";
- }
- }
-
- private void putline(String line)
- {
- try {
- Log.d("Spades", "< " + line);
- output.println(line);
- output.flush();
- } catch (Exception e) {
- Log.d("Spades", "Error writing line", e);
- this.running = false;
- }
- }
-
/* Public Methods */
public Client(String server, String nickname, String channel,
String username, String hostname)
this.channel = channel;
this.username = username;
this.hostname = hostname;
- Log.d("Spades", "Client create");
+ Os.debug("Client: create");
}
public Client(String server, String nickname, String channel)
{
this(server, nickname, channel, "user", "localhost");
- Log.d("Spades", "Client create");
}
public void connect(BufferedReader input, PrintWriter output)
{
this.input = input;
this.output = output;
- Log.d("Spades", "Client connect");
+ Os.debug("Client: connect");
putline("USER "+username+" "+hostname+" "+server+" :"+nickname);
putline("NICK "+nickname);
}
- public void send(String txt)
+ public Message send(String txt)
{
+ Message msg = new Message(channel, nickname, txt);
+ putline(msg.line);
+ return msg;
}
public Message recv()
{
try {
String line = getline();
- Message msg = new Message(line);
- process(msg);
- return msg;
+ if (line == null) {
+ this.running = false;
+ return null;
+ } else {
+ Message msg = new Message(line);
+ process(msg);
+ return msg;
+ }
} catch (Exception e) {
- Log.d("Spades", "Error in recv", e);
+ Os.debug("Client: error in recv", e);
this.running = false;
return null;
}
}
+
+ /* Private methods */
+ private void process(Message msg)
+ {
+ if (msg.cmd.equals("001") && msg.msg.matches("Welcome.*")) {
+ putline("JOIN " + channel);
+ putline("TOPIC " + channel);
+ }
+ if (msg.cmd.equals("PING")) {
+ putline("PING " + msg.msg);
+ }
+ }
+
+ private String getline()
+ {
+ try {
+ String line = input.readLine();
+ if (line != null)
+ Os.debug("> " + line);
+ return line;
+ } catch (Exception e) {
+ Os.debug("Client: error reading line", e);
+ this.running = false;
+ return "";
+ }
+ }
+
+ private void putline(String line)
+ {
+ try {
+ Os.debug("< " + line);
+ output.println(line);
+ output.flush();
+ } catch (Exception e) {
+ Os.debug("Client: error writing line", e);
+ this.running = false;
+ }
+ }
}
package org.pileus.spades;
-import java.io.*;
-import java.net.*;
-
import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
-import android.util.Log;
+import android.os.Handler;
+import android.os.Messenger;
+import android.text.method.ScrollingMovementMethod;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import android.widget.ScrollView;
+import android.widget.TabHost;
+import android.widget.TabWidget;
public class Main extends Activity
{
- /* Configuration */
- private String server = "irc.freenode.net";
- private String nickname = "andydroid";
- private String channel = "#rhnoise";
- private int port = 6667;
+ /* Static data */
+ private Handler handler;
+ private Messenger messenger;
/* Private data */
- private Socket socket = null;
- private Client client = null;
+ private Task task;
- /* Public Methods */
- @Override
- public void onCreate(Bundle savedInstanceState)
+ /* Widgets */
+ private TabHost window;
+ private TabWidget tabs;
+ private LinearLayout chat;
+ private TextView log;
+ private EditText input;
+ private Button send;
+ private TextView spades;
+ private TextView debug;
+
+ private ScrollView lscroll;
+ private ScrollView dscroll;
+
+ /* Private methods */
+ public void onRegister(Object obj)
{
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
+ Os.debug("Main: onRegister");
+ this.task = (Task)obj;
+ }
- try {
- this.socket = new Socket(server, port);
- this.client = new Client(server, nickname, channel);
- Log.d("Spades", "Socket and client created");
- } catch(Exception e) {
- Log.d("Spades", "Failed to create socket: " + e);
- return;
+ public void onMessage(Object obj)
+ {
+ Message msg = (Message)obj;
+
+ this.debug.append("> " + msg.line + "\n");
+ this.dscroll.smoothScrollTo(0, this.debug.getBottom());
+
+ if (msg.cmd.equals("PRIVMSG")) {
+ this.log.append(msg.from + ": " + msg.msg + "\n");
+ this.lscroll.smoothScrollTo(0, this.log.getBottom());
}
+ }
+ private void startService()
+ {
+ Os.debug("Main: startService");
+ startService(new Intent(this, Task.class)
+ .putExtra("Messenger", this.messenger));
+ }
+
+ private void stopService()
+ {
+ Os.debug("Main: stopService");
+ stopService(new Intent(this, Task.class));
+ }
+
+ /* Widget callback functions */
+ public void onSend(View btn)
+ {
+ if (this.task == null)
+ return;
+ String txt = this.input.getText().toString();
+ Message msg = this.task.send(txt);
+ if (msg == null)
+ return;
+ this.input.setText("");
+ this.log.append(msg.from + ": " + msg.msg + "\n");
+ }
+
+ /* Activity Methods */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
try {
- BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter output = new PrintWriter(socket.getOutputStream());
- this.client.connect(input, output);
- Log.d("Spades", "Client connected");
+ super.onCreate(savedInstanceState);
+ Os.debug("Main: onCreate");
+
+ // Setup main layout
+ this.setContentView(R.layout.main);
+
+ // Setup communication
+ this.handler = new MainHandler();
+ this.messenger = new Messenger(this.handler);
+
+ // Find widgets
+ this.window = (TabHost) findViewById(android.R.id.tabhost);
+ this.tabs = (TabWidget) findViewById(android.R.id.tabs);
+ this.chat = (LinearLayout) findViewById(R.id.chat);
+ this.log = (TextView) findViewById(R.id.log);
+ this.input = (EditText) findViewById(R.id.input);
+ this.send = (Button) findViewById(R.id.send);
+ this.spades = (TextView) findViewById(R.id.spades);
+ this.debug = (TextView) findViewById(R.id.debug);
+
+ this.lscroll = (ScrollView) findViewById(R.id.log_scroll);
+ this.dscroll = (ScrollView) findViewById(R.id.debug_scroll);
+
+ // Add window tabs
+ this.window.setup();
+
+ this.window.addTab(this.window
+ .newTabSpec("chat")
+ .setIndicator("Chat")
+ .setContent(R.id.chat));
+ this.window.addTab(this.window
+ .newTabSpec("spades")
+ .setIndicator("Spades")
+ .setContent(R.id.spades));
+ this.window.addTab(this.window
+ .newTabSpec("debug")
+ .setIndicator("Debug")
+ .setContent(R.id.debug));
+
+ // Start IRC service
+ this.startService();
+
} catch (Exception e) {
- Log.d("Spades", "Failed to create readers writers: " + e);
+ Os.debug("Error setting content view", e);
return;
}
+ }
+
+ @Override
+ public void onStart()
+ {
+ super.onStart();
+ Os.debug("Main: onStart");
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+ Os.debug("Main: onResume");
+ }
+
+ @Override
+ public void onPause()
+ {
+ super.onPause();
+ Os.debug("Main: onPause");
+ }
+
+ @Override
+ public void onStop()
+ {
+ super.onStop();
+ Os.debug("Main: onStop");
+ }
- TextView text = (TextView)findViewById(R.id.textview);
- while (client.running) {
- Message msg = client.recv();
- if (msg == null)
- continue;
+ @Override
+ public void onRestart()
+ {
+ super.onRestart();
+ Os.debug("Main: onRestart");
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+ Os.debug("Main: onDestroy");
+ }
+
+ /* Handler class */
+ class MainHandler extends Handler
+ {
+ public void handleMessage(android.os.Message msg)
+ {
+ switch (msg.what) {
+ case Task.REGISTER:
+ Main.this.onRegister(msg.obj);
+ break;
+ case Task.MESSAGE:
+ Main.this.onMessage(msg.obj);
+ break;
+ default:
+ Os.debug("Main: unknown message - " + msg.what);
+ break;
+ }
}
}
}
+
import java.util.regex.Pattern;
import java.util.regex.Matcher;
-import android.util.Log;
-
public class Message
{
/* Constnats */
private final String reMsg = "(:([^ ]+) +)?(([A-Z0-9]+) +)(([^ ]+) +)?(([^: ]+) +)?(:(.*))";
- private final String reFrom = "([^! ]+)!";
+ private final String reFrom = "([^! ]+)!.*";
private final String reTo = "(([^ :,]*)[:,] *)?(.*)";
private static Pattern ptMsg = null;
}
/* Public Methods */
- public Message(String line)
+ public Message(String dst, String from, String msg)
{
+ this.cmd = "PRIVMSG";
+ this.dst = dst;
+ this.from = from;
+ this.msg = msg;
+ this.line = this.cmd + " " + this.dst + " :" + this.msg;
+ }
+ public Message(String line)
+ {
if (ptMsg == null) ptMsg = Pattern.compile(reMsg);
if (ptFrom == null) ptFrom = Pattern.compile(reFrom);
if (ptTo == null) ptTo = Pattern.compile(reTo);
this.txt = notnull(this.msg);
else
this.txt = notnull(mrTo.group(3));
+
+ this.debug();
}
public void debug()
{
- Log.d("Spades", "---------------------");
- Log.d("Spades", "line = [" + line + "]");
- Log.d("Spades", "src = " + this.src);
- Log.d("Spades", "cmd = " + this.cmd);
- Log.d("Spades", "dst = " + this.dst);
- Log.d("Spades", "arg = " + this.arg);
- Log.d("Spades", "msg = " + this.msg);
- Log.d("Spades", "from = " + this.from);
- Log.d("Spades", "to = " + this.to);
- Log.d("Spades", "txt = " + this.txt);
- Log.d("Spades", "---------------------");
+ Os.debug("---------------------");
+ Os.debug("line = [" + line + "]");
+ Os.debug("src = " + this.src);
+ Os.debug("cmd = " + this.cmd);
+ Os.debug("dst = " + this.dst);
+ Os.debug("arg = " + this.arg);
+ Os.debug("msg = " + this.msg);
+ Os.debug("from = " + this.from);
+ Os.debug("to = " + this.to);
+ Os.debug("txt = " + this.txt);
+ Os.debug("---------------------");
+ }
+
+ public String toString()
+ {
+ return this.from + ": " + this.txt;
}
}
--- /dev/null
+package org.pileus.spades;
+
+import android.util.Log;
+
+public class Os
+{
+ /* Debugging */
+ public static void debug(String txt, Exception e)
+ {
+ Log.d("Spades", txt, e);
+ }
+ public static void debug(String txt)
+ {
+ Log.d("Spades", txt);
+ }
+}
--- /dev/null
+package org.pileus.spades;
+
+import java.io.*;
+import java.net.*;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Messenger;
+
+public class Task extends Service implements Runnable
+{
+ /* Commands */
+ public static final int REGISTER = 0;
+ public static final int MESSAGE = 1;
+
+ /* Configuration */
+ private String server = "irc.freenode.net";
+ private String nickname = "andydroid";
+ private String channel = "#rhnoise";
+ private int port = 6667;
+
+ /* Private data */
+ private Messenger messenger = null;
+ private Thread thread = null;
+ private Socket socket = null;
+ private Client client = null;
+
+ /* Private methods */
+ private void setup()
+ {
+ Os.debug("Task: setup");
+ try {
+ this.socket = new Socket(server, port);
+ this.client = new Client(server, nickname, channel);
+ Os.debug("Task: Socket and client created");
+ } catch(Exception e) {
+ Os.debug("Task: Failed to create socket: " + e);
+ return;
+ }
+
+ try {
+ BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ PrintWriter output = new PrintWriter(socket.getOutputStream());
+ this.client.connect(input, output);
+ Os.debug("Task: Client connected");
+ } catch (Exception e) {
+ Os.debug("Task: Failed to create readers writers: " + e);
+ return;
+ }
+ }
+
+ private void command(int cmd, Object value)
+ {
+ try {
+ android.os.Message msg = android.os.Message.obtain();
+ msg.what = cmd;
+ msg.obj = value;
+ this.messenger.send(msg);
+ } catch (Exception e) {
+ Os.debug("Task: error sending message");
+ }
+ }
+
+ /* Public methods */
+ public Message send(String txt)
+ {
+ if (this.client == null && !this.client.running)
+ return null;
+ return this.client.send(txt);
+ }
+
+ /* Runnable methods */
+ @Override
+ public void run()
+ {
+ Os.debug("Task: thread run");
+ setup();
+ while (client.running)
+ this.command(MESSAGE, client.recv());
+ Os.debug("Task: thread exit");
+ }
+
+ /* Service Methods */
+ @Override
+ public void onCreate()
+ {
+ Os.debug("Task: onCreate");
+ super.onCreate();
+
+ /* Setup notification bar */
+ Notification note = new Notification(android.R.drawable.presence_online, null, 0);
+ Intent intent = new Intent(this, Main.class);
+ PendingIntent pend = PendingIntent.getActivity(this, 0, intent, 0);
+
+ note.setLatestEventInfo(this, "Spades Title", "Spades Message", pend);
+ startForeground(1, note);
+
+ /* Start client thread */
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ Os.debug("Task: onDestroy");
+ super.onDestroy();
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId)
+ {
+ Os.debug("Task: onStart");
+ super.onStart(intent, startId);
+ this.messenger = (Messenger)intent.getExtras().get("Messenger");
+ this.command(REGISTER, this);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ Os.debug("Task: onBind");
+ return null;
+ }
+}