--- /dev/null
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
--- /dev/null
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.2.3'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
--- /dev/null
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
--- /dev/null
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "24.0.3"
+ defaultConfig {
+ applicationId "com.acmetensortoys.ctfwstimer"
+ minSdkVersion 19
+ targetSdkVersion 24
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+repositories {
+ maven {
+ url "https://repo.eclipse.org/content/repositories/paho-releases/"
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ wearApp project(':wear')
+
+ compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') {
+ exclude module: 'support-v4'
+ }
+ compile 'com.android.support:appcompat-v7:25.1.1'
+ compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
+ compile 'com.android.support:support-v4:25.1.1'
+ testCompile 'junit:junit:4.12'
+}
--- /dev/null
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/nwf/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Work around some Paho packaging warnings that manifest as errors.
+# See http://github.com/eclipse/paho.mqtt.android/issues/79
+-keepattributes InnerClasses
+-dontoptimize
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.acmetensortoys.ctfwstimer">
+
+ <!-- Required for Paho MQTT client -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <!-- Seems like a reasonable idea? -->
+ <!--
+ <uses-permission android:name="android.permission.VIBRATE" />
+ -->
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="org.eclipse.paho.android.service.MqttService" />
+ </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null
+package com.acmetensortoys.ctfwstimer;
+
+import android.util.Log;
+
+import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+class CtFwSCallbacksMQTT {
+ final private CtFwSDisplay mCdl;
+ final private CtFwSGameState mCgs;
+
+ CtFwSCallbacksMQTT(CtFwSDisplay cdl, CtFwSGameState cgs) {
+ mCdl = cdl;
+ mCgs = cgs;
+ }
+
+ IMqttMessageListener onConfig = new IMqttMessageListener() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ String tm = message.toString().trim();
+ Log.d("CtFwS", "Message(Config): " + tm);
+
+ switch (tm) {
+ case "none":
+ mCgs.configured = false;
+ break;
+ default:
+ try {
+ Scanner s = new Scanner(tm);
+ mCgs.startT = s.nextLong();
+ mCgs.setupD = s.nextInt();
+ mCgs.rounds = s.nextInt();
+ mCgs.roundD = s.nextInt();
+ mCgs.flagsTotal = s.nextInt();
+ mCgs.configured = true;
+ } catch (NoSuchElementException e) {
+ mCgs.configured = false;
+ }
+ break;
+ }
+ mCdl.notifyGameState();
+ }
+ };
+
+ IMqttMessageListener onEnd = new IMqttMessageListener() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ Log.d("CtFwS", "Message(End): " + message);
+ try {
+ mCgs.endT = Long.parseLong(message.toString());
+ } catch (NumberFormatException e) {
+ mCgs.endT = 0;
+ }
+ mCdl.notifyGameState();
+ }
+ };
+
+ IMqttMessageListener onFlags = new IMqttMessageListener() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ String tm = message.toString().trim();
+ Log.d("CtFwS", "Message(Flags): " + tm);
+
+ switch(tm) {
+ case "?":
+ mCgs.setFlags(false);
+ break;
+ default:
+ Scanner s = new Scanner(tm);
+ try {
+ mCgs.setFlags(true);
+ mCgs.setFlags(s.nextInt(),s.nextInt());
+ } catch (NumberFormatException e) {
+ mCgs.setFlags(false);
+ }
+ }
+ mCdl.notifyFlags();
+ }
+ };
+
+ IMqttMessageListener onMessage = new IMqttMessageListener() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ Log.d("CtFwS", "Message(Broadcast): " + message);
+ mCdl.notifyMessage(message.toString());
+ }
+ };
+
+ IMqttMessageListener onPlayerMessage = new IMqttMessageListener() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ Log.d("CtFwS", "Message(Players): " + message);
+ mCdl.notifyMessage(message.toString());
+ }
+ };
+}
--- /dev/null
+package com.acmetensortoys.ctfwstimer;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.widget.Chronometer;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+// TODO nwf is bad at UI design; someone who isn't him should improve this
+
+class CtFwSDisplay {
+ final private Activity mAct;
+ final private Handler mHandler;
+ final private CtFwSGameState mCgs;
+
+ private long lastMsgTimeMS = 0;
+
+ CtFwSDisplay(Activity a, Handler h, CtFwSGameState cgs) {
+ mAct = a;
+ mHandler = h;
+ mCgs = cgs;
+ }
+
+ final private Runnable mProber = new Runnable() {
+ @Override
+ public void run() {
+ notifyGameState();
+ }
+ };
+
+ void notifyGameState() {
+ final long nowMS = System.currentTimeMillis();
+ long nowET = SystemClock.elapsedRealtime(); // Chronometer timebase
+ final long tbcf = nowMS - nowET; // time base correction factor ("when we booted"-ish)
+
+ final CtFwSGameState.Now now = mCgs.getNow(nowMS / 1000);
+
+ Log.d("CtFwS", "Display game state; nowMS=" + nowMS + " r=" + now.round + " rs=" + now.roundStart + " re=" + now.roundEnd);
+
+ if (now.rationale != null) {
+ Log.d("CtFwS", "Rationale: " + now.rationale + " stop=" + now.stop);
+
+ // TODO: display rationale somewhere, probably by hiding the game state!
+
+ doReset();
+
+ mHandler.removeCallbacks(mProber);
+ if (!now.stop) {
+ mHandler.postDelayed(mProber, mCgs.startT*1000 - nowMS);
+ }
+
+ return;
+ }
+
+ // Otherwise, it's game on!
+
+ // Clear the mesage log if it looks like it's a new game in play
+ if (lastMsgTimeMS < mCgs.startT * 1000) {
+ final TextView msgs = (TextView) (mAct.findViewById(R.id.msgs));
+ msgs.post(new Runnable() {
+ @Override
+ public void run() {
+ msgs.setText("");
+ }
+ });
+ }
+
+ mHandler.removeCallbacks(mProber);
+ mHandler.postDelayed(mProber, now.roundEnd * 1000 - nowMS);
+
+ {
+ final TextView tv_jb = (TextView) (mAct.findViewById(R.id.tv_jailbreak));
+ tv_jb.post(new Runnable() {
+ @Override
+ public void run() {
+ if (now.round == 0) {
+ tv_jb.setText(R.string.ctfws_gamestart);
+ } else if (now.round == mCgs.rounds) {
+ tv_jb.setText(R.string.ctfws_gameend);
+ } else {
+ tv_jb.setText(
+ String.format(mAct.getResources().getString(R.string.ctfws_jailbreak),
+ now.round));
+ }
+ }
+ });
+
+ final ProgressBar pb_jb = (ProgressBar) (mAct.findViewById(R.id.pb_jailbreak));
+ pb_jb.post(new Runnable() {
+ @Override
+ public void run() {
+ pb_jb.setIndeterminate(false);
+ if (now.round == 0) {
+ pb_jb.setMax(mCgs.setupD - 1);
+ } else {
+ pb_jb.setMax(mCgs.roundD - 1);
+ }
+ pb_jb.setProgress(0);
+ }
+ });
+
+ final Chronometer ch_jb = (Chronometer) (mAct.findViewById(R.id.ch_jailbreak));
+ ch_jb.post(new Runnable() {
+ @Override
+ public void run() {
+ ch_jb.setBase(now.roundEnd * 1000 - tbcf);
+ ch_jb.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
+ @Override
+ public void onChronometerTick(Chronometer c) {
+ pb_jb.setProgress((int)(now.roundEnd - System.currentTimeMillis()/1000) - 1);
+ }
+ });
+ ch_jb.start();
+ }
+ });
+ }
+ if (now.round > 0){
+ final ProgressBar pb_gp = (ProgressBar) (mAct.findViewById(R.id.pb_gameProgress));
+ pb_gp.post(new Runnable() {
+ @Override
+ public void run() {
+ pb_gp.setIndeterminate(false);
+ pb_gp.setMax(mCgs.rounds * mCgs.roundD - 1);
+ pb_gp.setProgress(0);
+ }
+ });
+
+ final Chronometer ch_gp = (Chronometer) (mAct.findViewById(R.id.ch_gameProgress));
+ ch_gp.post(new Runnable() {
+ @Override
+ public void run() {
+ ch_gp.setBase((mCgs.startT + mCgs.setupD) * 1000 - tbcf);
+ ch_gp.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
+ @Override
+ public void onChronometerTick(Chronometer c) {
+ pb_gp.setProgress((int)(System.currentTimeMillis()/1000
+ - mCgs.startT - mCgs.setupD));
+ }
+ });
+ ch_gp.start();
+ }
+ });
+ } else {
+ final ProgressBar pb_gp = (ProgressBar) (mAct.findViewById(R.id.pb_gameProgress));
+ pb_gp.post(new Runnable() {
+ @Override
+ public void run() {
+ pb_gp.setIndeterminate(false);
+ pb_gp.setMax(mCgs.rounds * mCgs.roundD - 1);
+ pb_gp.setProgress(0);
+ }
+ });
+
+ final Chronometer ch_gp = (Chronometer) (mAct.findViewById(R.id.ch_gameProgress));
+ ch_gp.post(new Runnable() {
+ @Override
+ public void run() {
+ ch_gp.setBase(nowMS - tbcf);
+ ch_gp.setOnChronometerTickListener(null);
+ ch_gp.stop();
+ }
+ });
+ }
+ {
+ final TextView tv_flags = (TextView) (mAct.findViewById(R.id.tv_flags_label));
+ tv_flags.post(new Runnable() {
+ @Override
+ public void run() {
+ tv_flags.setText(
+ String.format(mAct.getResources().getString(R.string.ctfws_flags),
+ mCgs.flagsTotal));
+ }
+ });
+ }
+ }
+
+ private void doReset() {
+ Log.d("CtFwS", "Display Reset");
+
+ {
+ final Chronometer ch = (Chronometer) (mAct.findViewById(R.id.ch_jailbreak));
+ ch.post(new Runnable() {
+ @Override
+ public void run() {
+ ch.setOnChronometerTickListener(null);
+ ch.setBase(SystemClock.elapsedRealtime());
+ ch.stop();
+ }
+ });
+ }
+ {
+ final Chronometer ch = (Chronometer) (mAct.findViewById(R.id.ch_gameProgress));
+ ch.post(new Runnable() {
+ @Override
+ public void run() {
+ ch.setOnChronometerTickListener(null);
+ ch.setBase(SystemClock.elapsedRealtime());
+ ch.stop();
+ }
+ });
+ }
+ {
+ final ProgressBar pb = (ProgressBar) (mAct.findViewById(R.id.pb_jailbreak));
+ pb.post(new Runnable() {
+ @Override
+ public void run() {
+ pb.setIndeterminate(true);
+ }
+ });
+ }
+ {
+ final ProgressBar pb = (ProgressBar) (mAct.findViewById(R.id.pb_gameProgress));
+ pb.post(new Runnable() {
+ @Override
+ public void run() {
+ pb.setIndeterminate(true);
+ }
+ });
+ }
+ }
+
+ void notifyFlags() {
+ // TODO: This stinks
+
+ final StringBuffer sb = new StringBuffer();
+ if (mCgs.configured) {
+ if (mCgs.flagsVisible) {
+ sb.append("r=");
+ sb.append(mCgs.flagsRed);
+ sb.append(" y=");
+ sb.append(mCgs.flagsYel);
+ } else {
+ sb.append("r=? y=?");
+ }
+ }
+
+ final TextView msgs = (TextView)(mAct.findViewById(R.id.tv_flags));
+ msgs.post(new Runnable() {
+ @Override
+ public void run() {
+ msgs.setText(sb);
+ }
+ });
+ }
+
+ void notifyMessage(String m) {
+ lastMsgTimeMS = System.currentTimeMillis();
+
+ final StringBuffer sb = new StringBuffer();
+ long ts = lastMsgTimeMS/1000 - mCgs.startT;
+ if (!mCgs.configured || ts < 0) { ts = 0; }
+ sb.append(DateUtils.formatElapsedTime(ts));
+ sb.append(": ");
+ sb.append(m);
+ sb.append("\n");
+
+ final TextView msgs = (TextView)(mAct.findViewById(R.id.msgs));
+ msgs.post(new Runnable() {
+ @Override
+ public void run() {
+ msgs.append(sb);
+ }
+ });
+ }
+}
--- /dev/null
+package com.acmetensortoys.ctfwstimer;
+
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+class CtFwSGameState {
+ boolean configured;
+ long startT; // NTP seconds for game start
+ int setupD;
+ int rounds;
+ int roundD;
+ long endT = 0; // NTP seconds for game end (if >= startT)
+
+ int flagsTotal;
+ boolean flagsVisible = false;
+ int flagsRed = 0;
+ int flagsYel = 0;
+
+ void setFlags(boolean visible) {
+ flagsVisible = visible;
+ }
+ void setFlags(int red, int yel) {
+ flagsRed = red; flagsYel = yel;
+ }
+
+ class Now {
+ String rationale = null; // null if game is in play, otherwise other fields invalid
+ int round = 0; // 0 for setup
+ long roundStart = 0, roundEnd = 0; // NTP seconds
+ boolean stop = false;
+ }
+ public Now getNow(long now) {
+ Now res = new Now();
+ if (!configured) {
+ res.rationale = "Game not configured";
+ res.stop = true;
+ } else if (endT >= startT) {
+ res.rationale = "Game over!";
+ res.stop = true;
+ } else if (now <= startT) {
+ res.rationale = "Start time in the future!";
+ }
+ if (res.rationale != null) {
+ return res;
+ }
+ long elapsed = now - startT;
+ if (elapsed < setupD) {
+ res.round = 0;
+ res.roundStart = startT;
+ res.roundEnd = startT + setupD;
+ return res;
+ }
+ elapsed -= setupD;
+ res.round = (int)(elapsed / roundD);
+ if (res.round >= rounds) {
+ res.rationale = "Game over!";
+ res.stop = true;
+ return res;
+ }
+ res.roundStart = startT + setupD + (res.round * roundD);
+ res.roundEnd = res.roundStart + roundD;
+ res.round += 1;
+ return res;
+ }
+}
--- /dev/null
+package com.acmetensortoys.ctfwstimer;
+
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.eclipse.paho.android.service.MqttAndroidClient;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+public class MainActivity extends AppCompatActivity {
+
+ static private final String mqttClientId = MqttClient.generateClientId();
+ private MqttAndroidClient mMqc;
+
+ private final CtFwSGameState mCgs = new CtFwSGameState();
+ private CtFwSDisplay mCdl; // set in onCreate
+ private CtFwSCallbacksMQTT mCtfwscbs ; // set in onCreate
+
+ private synchronized void doMqtt(@Nullable String server) {
+ // Hang up on an existing connection, if we have one
+ synchronized (this) {
+ if (mMqc != null) {
+ mMqc.close();
+ }
+ mMqc = null;
+ mCgs.configured = false;
+ mCdl.notifyGameState();
+ }
+
+ // If that's all we were told to do, we're done
+ if (server == null) { return ; }
+
+ // We'll use this common callback object for our subscriptions below
+ final IMqttActionListener subal = new IMqttActionListener() {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ Log.d("CtFwS", "Sub OK: " + asyncActionToken);
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ Log.e("CtFws", "Sub Fail: " + asyncActionToken, exception);
+ }
+ };
+
+ // Make our MQTT client and grab callbacks on *everything in sight*
+ final MqttAndroidClient mqc = new MqttAndroidClient(this,server, mqttClientId);
+ mqc.setCallback(new MqttCallbackExtended() {
+ @Override
+ public void connectComplete(boolean reconnect, String serverURI) {
+ Log.d("CtFwS", "Conn OK 2 srv=" + serverURI + " reconn=" + reconnect);
+ try {
+ String p = "ctfws/game/";
+ mqc.subscribe(p+"config" , 2, null, subal, mCtfwscbs.onConfig);
+ mqc.subscribe(p+"endtime" , 2, null, subal, mCtfwscbs.onEnd);
+ mqc.subscribe(p+"flags" , 2, null, subal, mCtfwscbs.onFlags);
+ mqc.subscribe(p+"message" , 2, null, subal, mCtfwscbs.onMessage);
+ mqc.subscribe(p+"message/player", 2, null, subal, mCtfwscbs.onPlayerMessage);
+ } catch (MqttException e) {
+ Log.e("CtFwS", "Exn Sub", e);
+ }
+ }
+
+ @Override
+ public void connectionLost(Throwable cause) {
+ Log.d("CtFwS", "Conn Lost", cause);
+ }
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ Log.d("CtFwS", "Message(Generic) " + topic + " : '" + message + "'" );
+ }
+
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ // Unused, as we never publish
+ Log.d("CtFwS", "Delivery OK");
+ }
+ });
+
+
+ // Ahem. Now then. Connect with *more callbacks*, which will fire off our
+ // subscription requests, which of course have *yet more* callbacks, which
+ // react to messages sent to us. Have we lost the thread yet?
+ try {
+ MqttConnectOptions mco = new MqttConnectOptions();
+ mco.setAutomaticReconnect(true);
+ mco.setKeepAliveInterval(180); // seconds
+
+ mqc.connect(mco, null, new IMqttActionListener() {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ Log.d("CtFwS", "Conn OK 1");
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ Log.e("CtFws", "Conn Fail", exception);
+ }
+ });
+
+ } catch (MqttException e) {
+ Log.e("CtFwS", "Conn Exn", e);
+ }
+
+ synchronized (this) {
+ if (BuildConfig.DEBUG && mMqc != null) { throw new AssertionError(); }
+ mMqc = mqc;
+ }
+ }
+
+ // Must hold strongly since Android only holds weakly once registered.
+ private SharedPreferences.OnSharedPreferenceChangeListener mOSPCL
+ = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ switch(key) {
+ case "server":
+ doMqtt(sharedPreferences.getString("server",null));
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mCdl = new CtFwSDisplay(this, new Handler(), mCgs);
+ mCtfwscbs = new CtFwSCallbacksMQTT(mCdl, mCgs);
+
+ // TODO There really should be a UI thing for changing the server; we're all
+ // set for when-/if-ever that happens.
+
+ getPreferences(MODE_PRIVATE).registerOnSharedPreferenceChangeListener(mOSPCL);
+ doMqtt(getPreferences(MODE_PRIVATE).getString("server","tcp://nwf1.xen.prgmr.com:1883"));
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="com.acmetensortoys.ctfwstimer.MainActivity">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:text="@string/header_gamestate"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center" />
+
+ <TableLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:stretchColumns="1">
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:paddingBottom="1dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/tv_jailbreak"
+ android:text="@string/ctfws_gamestart"
+ android:gravity="center" />
+
+ <ProgressBar
+ style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/pb_jailbreak"
+ android:layout_weight="1"
+ android:indeterminate="false"
+ android:max="100"
+ android:progress="0"
+ android:padding="5dp"
+ android:rotation="180" />
+
+ <Chronometer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/ch_jailbreak"
+ android:countDown="true" />
+
+ </TableRow>
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:paddingBottom="1dp"
+ android:paddingTop="1dp">
+
+ <TextView
+ android:text="@string/header_gametimeela"
+ android:layout_height="wrap_content"
+ android:id="@+id/tv_gameProgress"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:layout_width="wrap_content" />
+
+ <ProgressBar
+ style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/pb_gameProgress"
+ android:layout_weight="1"
+ android:indeterminate="false"
+ android:padding="5dp" />
+
+ <Chronometer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/ch_gameProgress" />
+
+ </TableRow>
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/tr_flags"
+ android:paddingTop="1dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:id="@+id/tv_flags_label" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/tv_flags"
+ android:gravity="center" />
+
+ </TableRow>
+
+ </TableLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/header_messages" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="none"
+ android:ems="10"
+ android:id="@+id/msgs"
+ android:maxLines="10"
+ android:minLines="1"
+ android:scrollbars="vertical"
+ android:gravity="bottom" />
+ </LinearLayout>
+
+ <!--
+ <TableLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/tbl_opts"
+ android:stretchColumns="1">
+
+ <TableRow
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:text="Notifications\nDuring\nGame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <CheckBox
+ android:text="Wear"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/notif_wear"
+ android:layout_weight="1" />
+
+ <CheckBox
+ android:text="Here"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/notif_here"
+ android:layout_weight="1" />
+ </LinearLayout>
+
+ </TableRow>
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </TableLayout>
+ -->
+
+ </LinearLayout>
+
+</RelativeLayout>
--- /dev/null
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
--- /dev/null
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
--- /dev/null
+<resources>
+ <string name="app_name">CMUKGB CtFwS Timer</string>
+
+ <string name="ctfws_gameend">Game\nEnd</string>
+ <string name="ctfws_gamestart">Game\nStart</string>
+ <string name="ctfws_jailbreak">Jailbreak %1$d</string>
+ <string name="ctfws_flags">%1$d Flags:</string>
+
+ <string name="header_gamestate">Game State:</string>
+ <string name="header_gametimeela">Game\nTime\nElapsed</string>
+ <string name="header_messages">Messages:</string>
+</resources>
--- /dev/null
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+</resources>
--- /dev/null
+include ':mobile', ':wear'
--- /dev/null
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 24
+ buildToolsVersion "24.0.3"
+ defaultConfig {
+ applicationId "com.acmetensortoys.ctfwstimer"
+ minSdkVersion 21
+ targetSdkVersion 24
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.google.android.support:wearable:2.0.0-alpha3'
+ compile 'com.google.android.gms:play-services-wearable:10.0.1'
+}
--- /dev/null
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/nwf/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.acmetensortoys.ctfwstimer">
+ <uses-feature android:name="android.hardware.type.watch" />
+ <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name" android:supportsRtl="true"
+ android:theme="@android:style/Theme.DeviceDefault">
+
+ </application>
+
+</manifest>
--- /dev/null
+<resources>
+ <string name="app_name">CtFwS Timer</string>
+</resources>