]> hydra-www.ietfng.org Git - acmetensortoys-ctfws-android/commitdiff
A bunch of changes
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 10 Nov 2017 23:03:50 +0000 (18:03 -0500)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Fri, 10 Nov 2017 23:04:25 +0000 (18:04 -0500)
Add stun timers
Try to fix up spurious message delivery (working around Paho issues)
Bump version to 1.0.1

12 files changed:
build.gradle
mobile/build.gradle
mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSCallbacksMQTT.java
mobile/src/main/java/com/acmetensortoys/ctfwstimer/CtFwSDisplayLocal.java
mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainActivity.java
mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainService.java
mobile/src/main/java/com/acmetensortoys/ctfwstimer/MainServiceNotification.java
mobile/src/main/res/layout/activity_main.xml
mobile/src/main/res/menu/mainmenu.xml
mobile/src/main/res/values/strings.xml
mobile/src/main/res/xml/preferences.xml
wear/build.gradle

index c2eea8e27fd12cc1e274a0940f06f350e855e20f..c33a638bda0d9801bbe8ec3c2de2ddf2e9e2e83f 100644 (file)
@@ -5,7 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.3'
+        classpath 'com.android.tools.build:gradle:3.0.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
index 370b215c47d8f453f035051046edf14dec99d96e..051921f8258686b9cd07bc2bc78332c3b454969d 100644 (file)
@@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion 25
-    buildToolsVersion "25.0.3"
+    buildToolsVersion '26.0.2'
     defaultConfig {
         applicationId "com.acmetensortoys.ctfwstimer"
         minSdkVersion 16
         targetSdkVersion 25
-        versionCode 6
-        versionName "1.0"
+        versionCode 7
+        versionName "1.0.1"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
     buildTypes {
index 25bf3ff0450026e29a63ee6a745d5a58d87b085b..94ccc9838777abeb3b0a17b25082d8d97712bb5d 100644 (file)
@@ -8,12 +8,16 @@ import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
 import org.eclipse.paho.client.mqttv3.MqttMessage;
 
 class CtFwSCallbacksMQTT {
-    final private CtFwSGameState mCgs;
+    private CtFwSGameState mCgs;
 
     CtFwSCallbacksMQTT(CtFwSGameState cgs) {
         mCgs = cgs;
     }
 
+    final public void dispose() {
+        mCgs = null;
+    }
+
     final IMqttMessageListener onConfig = new IMqttMessageListener() {
         @Override
         public void messageArrived(String topic, MqttMessage message) throws Exception {
index f2f9f86a5130e31bc74a08334247e59265429ab0..81c0356a34af0b7dfa286cc6794ed950262d2981 100644 (file)
@@ -44,7 +44,7 @@ class CtFwSDisplayLocal implements CtFwSGameState.Observer {
     }
 
     private void wireTimer(int vid, final StunTimer st) {
-        ((Button)mAct.findViewById(vid))
+        mAct.findViewById(vid)
                 .setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
@@ -160,6 +160,7 @@ class CtFwSDisplayLocal implements CtFwSGameState.Observer {
                             pb_jb.setProgress((int) (now.roundEnd - System.currentTimeMillis() / 1000));
                         }
                     });
+                    ch_jb.setVisibility(View.VISIBLE);
                     ch_jb.start();
                 }
             });
@@ -235,6 +236,7 @@ class CtFwSDisplayLocal implements CtFwSGameState.Observer {
                     ch.setOnChronometerTickListener(null);
                     ch.setBase(SystemClock.elapsedRealtime());
                     ch.stop();
+                    ch.setVisibility(View.INVISIBLE);
                 }
             });
         }
@@ -344,12 +346,20 @@ class CtFwSDisplayLocal implements CtFwSGameState.Observer {
         resumeTimer(st, wallStart + st.ms);
     }
 
+    private void hideTimer(final StunTimer st) {
+        st.ch.setOnChronometerTickListener(null);
+        st.ch.setVisibility(View.INVISIBLE);
+        st.pb.setVisibility(View.INVISIBLE);
+    }
+
     private void resumeTimer(final StunTimer st, final long wallEnd) {
+        Log.d("CtFwS", "Timer start: " + st.ms);
+        st.wallEndMS = wallEnd;
+
         final long nowWall = System.currentTimeMillis();
-        if (nowWall < wallEnd) {
-            st.ch.setOnChronometerTickListener(null);
-            st.ch.setVisibility(View.INVISIBLE);
-            st.pb.setVisibility(View.INVISIBLE);
+        if (wallEnd < nowWall) {
+            Log.d("CtFwS", "Timer finished in past");
+            hideTimer(st);
             return;
         }
 
@@ -362,10 +372,13 @@ class CtFwSDisplayLocal implements CtFwSGameState.Observer {
             public void onChronometerTick(Chronometer chronometer) {
                 final long nowAbsCB = System.currentTimeMillis();
                 st.pb.setProgress((int) (wallEnd - nowAbsCB));
+                if (wallEnd < nowAbsCB) {
+                    hideTimer(st);
+                }
             }
         });
 
-        st.pb.setProgress((int) (wallEnd - nowWall));
+        st.pb.setMax((int) (wallEnd - nowWall));
         st.ch.start();
         st.ch.setVisibility(View.VISIBLE);
         st.pb.setVisibility(View.VISIBLE);
index 677ba891523d7e0b48da8f200f54bd04513fb2dc..f6597f4a9c9e5b4775e47aa2e97a342383cb1d97 100644 (file)
@@ -6,6 +6,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
 import android.support.annotation.StringRes;
@@ -18,8 +19,6 @@ import android.view.MenuItem;
 import android.view.View;
 import android.widget.TextView;
 
-// TODO There should be an I've-been-stunned timer, too.
-
 public class MainActivity extends AppCompatActivity {
     private final MainActivityBuildHooks mabh = new MainActivityBuildHooksImpl();
 
@@ -64,6 +63,9 @@ public class MainActivity extends AppCompatActivity {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
+        // TODO: should probably look better in landscape, too.
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
         if (sp.getString("server", null) == null) {
             sp.edit().putString("server", getString(R.string.server_default)).apply();
@@ -165,7 +167,9 @@ public class MainActivity extends AppCompatActivity {
 
     // Kick the mqtt layer on a click on the status stuff
     public void onclick_connmeta(@SuppressWarnings("UnusedParameters") View v) {
-        mSrvBinder.connect(true);
+        if (mSrvBinder != null) {
+            mSrvBinder.connect(true);
+        }
     }
 
     // TODO should we be using onClick instead for routing?
@@ -174,6 +178,11 @@ public class MainActivity extends AppCompatActivity {
     @Override
     public boolean onOptionsItemSelected(MenuItem mi) {
         switch(mi.getItemId()) {
+            case R.id.menu_reconn:
+                if (mSrvBinder != null) {
+                    mSrvBinder.connect(true);
+                }
+                return true;
             case R.id.menu_prf :
                 startActivity(new Intent(this, SettingsActivity.class));
                 return true;
index 6501af2361d55ff7da45adf7bb103162d2a892e6..e4cfcb172bca26d1d234e3177d36935279704053 100644 (file)
@@ -51,7 +51,6 @@ public class MainService extends Service {
             mHandler.removeCallbacks(r);
         }
     });
-    private final CtFwSCallbacksMQTT mCtfwscbs = new CtFwSCallbacksMQTT(mCgs);
 
     @SuppressWarnings({"FieldCanBeLocal", "unused"})
     private MainServiceNotification mMsn; // set in onCreate
@@ -89,14 +88,17 @@ public class MainService extends Service {
         @Override
         public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
             Log.e("CtFws", "Sub Fail: " + asyncActionToken, exception);
-            setMSE(MqttServerEvent.MSE_CONN);
         }
     };
     // And this handles making our subscriptions for us
-    private final MqttCallbackExtended mqttcb = new MqttCallbackExtended() {
+    private class MyMQTTCallbacks implements MqttCallbackExtended {
+        public CtFwSCallbacksMQTT mCtfwscbs;
+
         @Override
         public void connectComplete(boolean reconnect, String serverURI) {
             Log.d("CtFwS", "Conn OK 2 srv=" + serverURI + " reconn=" + reconnect);
+             mCtfwscbs = new CtFwSCallbacksMQTT(mCgs);
+
             String p = "ctfws/game/";
             try {
                 mMqc.subscribe(p + "config", 2, null, subal, mCtfwscbs.onConfig);
@@ -113,6 +115,8 @@ public class MainService extends Service {
         @Override
         public void connectionLost(Throwable cause) {
             Log.d("CtFwS", "Conn Lost: " + cause, cause);
+            mCtfwscbs.dispose();
+            mCtfwscbs = null;
             setMSE(MqttServerEvent.MSE_DISCONN);
         }
 
@@ -127,7 +131,9 @@ public class MainService extends Service {
             Log.d("CtFwS", "Delivery OK");
         }
     };
-    // And this handles yet more about connecting
+    private final MyMQTTCallbacks mqttcb = new MyMQTTCallbacks();
+
+        // And this handles yet more about connecting
     private final IMqttActionListener mqttal = new IMqttActionListener() {
         @Override
         public void onSuccess(IMqttToken asyncActionToken) {
@@ -138,7 +144,8 @@ public class MainService extends Service {
             } else {
                 Log.d("Service", "IS STALE CONN");
                 try {
-                    c.disconnect().waitForCompletion();
+                    // TODO Should we waitforcompletion here?
+                    c.disconnect();
                 } catch (MqttException me) {
                     // Drop it, we've already dropped the client handle
                 }
@@ -163,6 +170,19 @@ public class MainService extends Service {
         if (mMqc != null) {
             mMqc.setCallback(null);
 
+            // Observationally, it looks like .close() below isn't enough!  Deliberately
+            // fling unsubscriptions at the server.
+            try {
+                String p = "ctfws/game/";
+                mMqc.unsubscribe(new String[]{
+                        p + "config", p + "endtime", p + "flags",
+                        p + "message", p + "message/player"
+                });
+            } catch (MqttException me) {
+                Log.d("Service", "domqtt discon unsub exn");
+                // *&@#&^*#@#&@#&@#
+            }
+
             // TODO: This is *really* annoying; we might leak a connection here because
             // .disconnect() is so @#*@&#*@^#*&@^ asynchronous it hurts.  There appears
             // to be no way to force its hand, and adding .waitforcompletion() here just
@@ -176,10 +196,21 @@ public class MainService extends Service {
                 // *&@#&^*#@#&@#&@#
             }
             mMqc.unregisterResources();
+
         } else {
             Log.d("Service", "domqtt no client");
         }
 
+        // At this point, prevent the client we just shot down from making any further changes
+        // to the state machine.  This is a little grody, since you'd think we'd just have done
+        // just that, what with all the disconnecting and the closing and unregistering of
+        // callbacks, but Paho is a steaming pile of Enterprise Code and apparently loves to
+        // hold on to things.  So we are about to force a lot of NPEs by nulling out our callback
+        // holder's reference to the game state.
+        if (mqttcb.mCtfwscbs != null) {
+            mqttcb.mCtfwscbs.dispose();
+        }
+
         // If we're deliberately disconnecting, tell the service about it.  Otherwise, we'll
         // just keep doing what we're doing until we get some message telling us to do something
         // else. :)
@@ -213,7 +244,7 @@ public class MainService extends Service {
         MqttConnectOptions mco = new MqttConnectOptions();
         mco.setCleanSession(true);
         mco.setAutomaticReconnect(true);
-        mco.setKeepAliveInterval(180); // seconds
+        mco.setKeepAliveInterval(10); // seconds
         try {
             mMqc.connect(mco, null, mqttal);
         } catch (MqttException e) {
@@ -235,9 +266,9 @@ public class MainService extends Service {
 
     // MQTT Observers
     public enum MqttServerEvent {
-        MSE_DISCONN,
-        MSE_CONN,
-        MSE_SUB,
+        MSE_DISCONN,    /* No active connection */
+        MSE_CONN,       /* Connected, but not subscribed */
+        MSE_SUB,        /* Subscriptions have been registered */
     }
     private MqttServerEvent mMSE = MqttServerEvent.MSE_DISCONN;
     public interface Observer {
@@ -282,7 +313,7 @@ public class MainService extends Service {
             }
         }
         void registerObserver(Observer o) {
-            synchronized(this) {
+            synchronized(MainService.this) {
                 if (mObsvs.add(o)) {
                     // Fire off synthetic deltas to bring the observer up to date.
                     if (mMqc == null) {
@@ -295,7 +326,7 @@ public class MainService extends Service {
             }
         }
         void unregisterObserver(Observer o) {
-            synchronized(this) { mObsvs.remove(o); }
+            synchronized(MainService.this) { mObsvs.remove(o); }
         }
     }
     private final LocalBinder mBinder = new LocalBinder();
index 5e025ec30af0790565bb7dc05515bbccac20f4d7..f9bd49df2ca3b27245c05cf700f25960ac78ed9e 100644 (file)
@@ -6,6 +6,8 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.res.Resources;
+import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Build;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
@@ -21,7 +23,7 @@ class MainServiceNotification {
 
     private long lastVibrateTime;
 
-    private enum VibrationSource { NONE, BREAK, FLAG, MESG }
+    private enum NotificationSource { NONE, BREAK, FLAG, MESG }
     private enum LastContentTextSource { NONE, FLAG, MESG }
     private LastContentTextSource lastContextTextSource = LastContentTextSource.NONE;
 
@@ -69,7 +71,7 @@ class MainServiceNotification {
                         userNoteBuilder.setSubText(now.rationale);
                     }
 
-                    vibrate(VibrationSource.BREAK);
+                    notifyUserSomehow(NotificationSource.BREAK);
                     ensureNotification();
                 } else {
                     // game no longer afoot
@@ -85,7 +87,7 @@ class MainServiceNotification {
                 if (game.flagsVisible
                         && ((lastContextTextSource == LastContentTextSource.FLAG)
                             || (game.flagsRed + game.flagsYel > 0))) {
-                    vibrate(VibrationSource.FLAG);
+                    notifyUserSomehow(NotificationSource.FLAG);
                     lastContextTextSource = LastContentTextSource.FLAG;
                     userNoteBuilder.setContentText(
                             String.format(mService.getResources().getString(R.string.notify_flags),
@@ -99,7 +101,7 @@ class MainServiceNotification {
                 // Only do anything if we aren't clearing the message list
                 int s = msgs.size();
                 if (s != 0) {
-                    vibrate(VibrationSource.MESG);
+                    notifyUserSomehow(NotificationSource.MESG);
                     lastContextTextSource = LastContentTextSource.MESG;
                     userNoteBuilder.setContentText(msgs.get(s - 1).msg);
                     refreshNotification();
@@ -109,34 +111,42 @@ class MainServiceNotification {
     }
 
     // TODO make all of these configurable?
-    private final long   VIBRATE_SUPPRESS_THRESHOLD = 5000; // suppress rapid-fire buzzing
+    private final long NOTIFY_SUPPRESS_THRESHOLD = 5000; // suppress rapid-fire buzzing
+
     private final long[] VIBRATE_PATTERN_NOW  = {0, 100, 100, 300, 100, 300, 100, 300}; // 'J' = .---
     private final long[] VIBRATE_PATTERN_FLAG = {0, 100, 100, 100, 100, 300, 100, 100}; // 'F' = ..-.
     private final long[] VIBRATE_PATTERN_MSG  = {0, 300, 100, 300};                     // 'M' = --
 
-    private void vibrate(VibrationSource vs) {
+
+    private void notifyUserSomehow(NotificationSource vs) {
         long now = System.currentTimeMillis();
 
         // Clobber the vibration request if we probably recently did such a thing
-        if ((now - lastVibrateTime < VIBRATE_SUPPRESS_THRESHOLD)) {
-            vs = VibrationSource.NONE;
+        if ((now - lastVibrateTime < NOTIFY_SUPPRESS_THRESHOLD)) {
+            vs = NotificationSource.NONE;
         }
 
-        String pref;
-        long[] pattern;
+        String vpref;
+        long[] vpattern;
+
+        String spref;
+        Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
 
         switch(vs) {
             case BREAK:
-                pref = "prf_vibr_jb";
-                pattern = VIBRATE_PATTERN_NOW;
+                vpref = "prf_vibr_jb";
+                spref = "prf_sound_jb";
+                vpattern = VIBRATE_PATTERN_NOW;
                 break;
             case FLAG:
-                pref = "prf_vibr_flag";
-                pattern = VIBRATE_PATTERN_FLAG;
+                vpref = "prf_vibr_flag";
+                spref = "prf_sound_flag";
+                vpattern = VIBRATE_PATTERN_FLAG;
                 break;
             case MESG:
-                pref = "prf_vibr_mesg";
-                pattern = VIBRATE_PATTERN_MSG;
+                vpref = "prf_vibr_mesg";
+                spref = "prf_sound_mesg";
+                vpattern = VIBRATE_PATTERN_MSG;
                 break;
             case NONE:
             default:
@@ -147,13 +157,20 @@ class MainServiceNotification {
         // Cam: default value is "false" because we really don't want to be vibrating if we
         //      accidentally lose our preferences somehow
         if (PreferenceManager.getDefaultSharedPreferences(mService.getBaseContext())
-                .getBoolean(pref, false)) {
-            userNoteBuilder.setVibrate(pattern);
+                .getBoolean(vpref, false)) {
+            userNoteBuilder.setVibrate(vpattern);
             lastVibrateTime = now;
         }
         else {
             userNoteBuilder.setVibrate(null);
         }
+
+        if (PreferenceManager.getDefaultSharedPreferences(mService.getBaseContext())
+            .getBoolean(spref, false)) {
+            userNoteBuilder.setSound(soundUri);
+        } else {
+            userNoteBuilder.setSound(null);
+        }
     }
 
     private ServiceConnection userNoteSC;
index 4f5d77bcd9a4e6ce098103c2ea202dd442e7eeff..d1072a7217239d69e2e2927cd3c792606da0f352 100644 (file)
@@ -18,7 +18,6 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:clickable="true"
             android:onClick="onclick_gamestate"
             android:id="@+id/header_gamestate" />
 
                     android:rotation="180" />
 
                 <Chronometer
+                    android:id="@+id/ch_jailbreak"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:id="@+id/ch_jailbreak"
-                    android:layout_gravity="center_vertical" />
+                    android:layout_gravity="center_vertical"
+                    android:visibility="invisible" />
 
             </TableRow>
 
 
         </TableLayout>
 
-        <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:lines="10"
-            android:scrollbars="vertical"
-            android:gravity="bottom" />
-
         <TableLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content">
 
             <TableRow
                 android:layout_width="match_parent"
-                android:layout_height="match_parent" >
+                android:layout_height="match_parent">
 
                 <Button
                     android:id="@+id/btn_wait_short"
                     android:layout_weight="1"
                     android:indeterminate="false"
                     android:padding="5dp"
-                    android:visibility="invisible" />
+                    android:visibility="invisible"
+                    android:rotation="180"/>
 
                 <Chronometer
                     android:id="@+id/ch_wait_short"
 
             <TableRow
                 android:layout_width="match_parent"
-                android:layout_height="match_parent" >
+                android:layout_height="match_parent">
 
                 <Button
                     android:id="@+id/btn_wait_long"
                     android:layout_weight="1"
                     android:indeterminate="false"
                     android:padding="5dp"
-                    android:visibility="invisible" />
+                    android:visibility="invisible"
+                    android:rotation="180"/>
 
                 <Chronometer
                     android:id="@+id/ch_wait_long"
             </TableRow>
 
         </TableLayout>
+
+        <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:lines="10"
+            android:scrollbars="vertical"
+            android:gravity="bottom" />
+
     </LinearLayout>
 
     <LinearLayout
index eb5c15ea1ad5d28b033297fc021ebcf0f838b836..d5837ef5852ffee9143eda087782322c29b3fb85 100644 (file)
@@ -1,17 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:android="http://schemas.android.com/apk/res/android">
 
     <item
-        android:visible="true"
+        android:id="@+id/menu_reconn"
+        android:checkable="false"
         android:enabled="true"
-        android:title="@string/menutext_prf"
+        android:icon="@android:drawable/ic_menu_search"
+        android:title="@string/menutext_reconn"
+        android:visible="true"
+        app:showAsAction="ifRoom" />
+    <item
         android:id="@+id/menu_prf"
+        android:checkable="false"
+        android:enabled="true"
         android:icon="@android:drawable/ic_menu_manage"
-        android:checkable="false"/>
-    <item android:title="@string/menutext_about"
-        android:id="@+id/menu_about"
-        android:icon="@android:drawable/ic_menu_compass"
+        android:title="@string/menutext_prf"
         android:visible="true"
+        app:showAsAction="never" />
+    <item
+        android:id="@+id/menu_about"
+        android:checkable="false"
         android:enabled="true"
-        android:checkable="false" />
+        android:icon="@android:drawable/ic_menu_help"
+        android:title="@string/menutext_about"
+        android:visible="true" />
 </menu>
\ No newline at end of file
index d59fe4c0ba262e4e927c21397dfa4781bb536ca8..5c22521344b51626fa39935293d04d2dffa5c1c9 100644 (file)
     <string name="preftext_vibrate_jb">Vibrate on Jailbreak?</string>
     <string name="preftext_vibrate_flag">Vibrate on Flag Capture?</string>
     <string name="preftext_vibrate_mesg">Vibrate on Message?</string>
+    <string name="preftext_sound_jb">Sound on Jailbreak?</string>
+    <string name="preftext_sound_flag">Sound on Flag Capture?</string>
+    <string name="preftext_sound_mesg">Sound on Message?</string>
 
     <string name="string_null">&lt;&lt;null&gt;&gt;</string>
 
-    <string name="wait_long">Wait 60</string>
-    <string name="wait_short">Wait 10</string>
+    <string name="wait_long">Stun 60</string>
+    <string name="wait_short">Stun 10</string>
 
     <string name="about_imagealt">The CMUKGB Shield Logo</string>
     <string name="about_text"><![CDATA[
@@ -52,4 +55,5 @@
         </center>
         ]]>
     </string>
+    <string name="menutext_reconn">Reconnect</string>
 </resources>
index 04c91d66a9536ec0debb4813d5323af240336e47..bac8d3cefffa239d2bcc02cf6eebc8cc578a0d6a 100644 (file)
@@ -7,20 +7,40 @@
         android:key="server"\r
         android:defaultValue="@string/server_default"\r
         android:title="@string/preftext_mqtt" />\r
+\r
     <CheckBoxPreference\r
         android:defaultValue="true"\r
         android:key="prf_vibr_jb"\r
         android:title="@string/preftext_vibrate_jb" />\r
+    <CheckBoxPreference\r
+        android:defaultValue="true"\r
+        android:key="prf_sound_jb"\r
+        android:title="@string/preftext_sound_jb" />\r
+\r
     <CheckBoxPreference\r
         android:layout_width="wrap_content"\r
         android:layout_height="wrap_content"\r
         android:defaultValue="true"\r
         android:key="prf_vibr_flag"\r
         android:title="@string/preftext_vibrate_flag" />\r
+    <CheckBoxPreference\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:defaultValue="true"\r
+        android:key="prf_sound_flag"\r
+        android:title="@string/preftext_sound_flag" />\r
+\r
     <CheckBoxPreference\r
         android:layout_width="wrap_content"\r
         android:layout_height="wrap_content"\r
         android:defaultValue="true"\r
         android:key="prf_vibr_mesg"\r
         android:title="@string/preftext_vibrate_mesg" />\r
+    <CheckBoxPreference\r
+        android:layout_width="wrap_content"\r
+        android:layout_height="wrap_content"\r
+        android:defaultValue="true"\r
+        android:key="prf_vibr_mesg"\r
+        android:title="@string/preftext_sound_mesg" />\r
+\r
 </PreferenceScreen>\r
index f859f246dfc51a01ff71a4acb5f91f61df8245e2..7634680aa76b822798ac497b441e1af2fb8c4e12 100644 (file)
@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion 25
-    buildToolsVersion "25.0.3"
+    buildToolsVersion '26.0.2'
     defaultConfig {
         applicationId "com.acmetensortoys.ctfwstimer"
         minSdkVersion 21