From: Nathaniel Wesley Filardo Date: Thu, 2 Feb 2017 23:42:05 +0000 (-0500) Subject: Initial revision X-Git-Tag: release-1.2~58 X-Git-Url: https://hydra-www.ietfng.org/gitweb/?a=commitdiff_plain;h=8f14343c25f334f1e29d306bb370b442cbd6b508;p=acmetensortoys-ctfws-android Initial revision Logic seems to work, UI is super terrible --- 8f14343c25f334f1e29d306bb370b442cbd6b508 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..74b2ab0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +// 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 +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# 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 diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mobile/build.gradle b/mobile/build.gradle new file mode 100644 index 0000000..31f6c5a --- /dev/null +++ b/mobile/build.gradle @@ -0,0 +1,46 @@ +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' +} diff --git a/mobile/proguard-rules.pro b/mobile/proguard-rules.pro new file mode 100644 index 0000000..4e7b00d --- /dev/null +++ b/mobile/proguard-rules.pro @@ -0,0 +1,22 @@ +# 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 diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ba66053 --- /dev/null +++ b/mobile/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSCallbacksMQTT.java b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSCallbacksMQTT.java new file mode 100644 index 0000000..c0b5540 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSCallbacksMQTT.java @@ -0,0 +1,99 @@ +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()); + } + }; +} diff --git a/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSDisplay.java b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSDisplay.java new file mode 100644 index 0000000..4d3b3ca --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSDisplay.java @@ -0,0 +1,268 @@ +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); + } + }); + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSGameState.java b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSGameState.java new file mode 100644 index 0000000..3c12b2e --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSGameState.java @@ -0,0 +1,65 @@ +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; + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainActivity.java b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainActivity.java new file mode 100644 index 0000000..a9d3ea2 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainActivity.java @@ -0,0 +1,149 @@ +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")); + } +} diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..381648e --- /dev/null +++ b/mobile/src/main/res/layout/activity_main.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/mobile/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/mobile/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/src/main/res/values-w820dp/dimens.xml b/mobile/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/mobile/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/mobile/src/main/res/values/colors.xml b/mobile/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/mobile/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/mobile/src/main/res/values/dimens.xml b/mobile/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/mobile/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml new file mode 100644 index 0000000..c385cc9 --- /dev/null +++ b/mobile/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + CMUKGB CtFwS Timer + + Game\nEnd + Game\nStart + Jailbreak %1$d + %1$d Flags: + + Game State: + Game\nTime\nElapsed + Messages: + diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/mobile/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6a4e79f --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':mobile', ':wear' diff --git a/wear/.gitignore b/wear/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/wear/.gitignore @@ -0,0 +1 @@ +/build diff --git a/wear/build.gradle b/wear/build.gradle new file mode 100644 index 0000000..16de14e --- /dev/null +++ b/wear/build.gradle @@ -0,0 +1,25 @@ +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' +} diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro new file mode 100644 index 0000000..23c6bbc --- /dev/null +++ b/wear/proguard-rules.pro @@ -0,0 +1,17 @@ +# 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 *; +#} diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0dfd77f --- /dev/null +++ b/wear/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/wear/src/main/res/mipmap-hdpi/ic_launcher.png b/wear/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/wear/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-mdpi/ic_launcher.png b/wear/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/wear/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-xhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/wear/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/wear/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml new file mode 100644 index 0000000..e54a99b --- /dev/null +++ b/wear/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + CtFwS Timer +