From 0c405dfd0856b09b65d38f1884e4932d31a7a3ce Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Sat, 20 Aug 2016 12:46:04 -0400 Subject: [PATCH] Hack and slash and more refactoring Maybe making progress? --- .gitignore | 6 + build.gradle | 2 +- mobile/build.gradle | 36 +- mobile/src/main/AndroidManifest.xml | 38 ++- .../android/teled/IOIO/BehaviorFactories.java | 133 ++++++++ .../android/teled/IOIO/BehaviorFactory.java | 25 ++ .../android/teled/IOIO/TeleDIOIOManager.java | 121 +++++++ .../android/teled/IOIOBehaviors.java | 105 ------ .../android/teled/IOIOService.java | 143 -------- .../android/teled/MainActivity.java | 176 ---------- .../android/teled/RemoteControlService.java | 78 ----- .../acmetensortoys/android/teled/SMSRecv.java | 31 -- .../teled/Service/EphemeralTeleDService.java | 67 ++++ .../teled/Service/PositionReporting.java | 18 + .../android/teled/Service/SMSRecv.java | 65 ++++ .../android/teled/Service/TeleDService.java | 202 ++++++++++++ .../android/teled/UI/FragPrefs.java | 65 ++++ .../android/teled/UI/MainActivity.java | 312 ++++++++++++++++++ .../teled/Utils/Android/LocationUpdateBH.java | 37 +++ .../teled/Utils/Android/PendingIntentBH.java | 100 ++++++ .../teled/Utils/Android/RecurrentAlarmBH.java | 33 ++ .../android/teled/Utils/BehaviorHandle.java | 14 + .../teled/Utils/BehaviorHandleSet.java | 90 +++++ .../android/teled/Utils/Subscribee.java | 9 + .../android/teled/Utils/SubscribeeImpl.java | 34 ++ .../android/teled/Utils/SuspendableBH.java | 6 + .../teled/Utils/ThreadBehaviorHandle.java | 50 +++ .../main/res/drawable/ic_info_black_24dp.xml | 9 + .../drawable/ic_notifications_black_24dp.xml | 9 + .../main/res/drawable/ic_sync_black_24dp.xml | 9 + mobile/src/main/res/layout/app_bar_main.xml | 2 +- .../src/main/res/layout/content_scrolling.xml | 2 +- mobile/src/main/res/layout/fragment_main.xml | 49 +++ mobile/src/main/res/layout/newmain.xml | 15 + mobile/src/main/res/values-v21/styles.xml | 9 - mobile/src/main/res/values-w820dp/dimens.xml | 6 - mobile/src/main/res/values/strings.xml | 3 + mobile/src/main/res/values/styles.xml | 2 +- mobile/src/main/res/xml/pref_frag_main.xml | 53 +++ wear/build.gradle | 6 +- wear/src/main/res/layout/activity_main.xml | 2 +- 41 files changed, 1593 insertions(+), 579 deletions(-) create mode 100644 .gitignore create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactories.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactory.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/TeleDIOIOManager.java delete mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java delete mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java delete mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java delete mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java delete mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Service/EphemeralTeleDService.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Service/PositionReporting.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Service/SMSRecv.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Service/TeleDService.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/UI/FragPrefs.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/UI/MainActivity.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/LocationUpdateBH.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/PendingIntentBH.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/RecurrentAlarmBH.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandle.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandleSet.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Subscribee.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SubscribeeImpl.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SuspendableBH.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Utils/ThreadBehaviorHandle.java create mode 100644 mobile/src/main/res/drawable/ic_info_black_24dp.xml create mode 100644 mobile/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 mobile/src/main/res/drawable/ic_sync_black_24dp.xml create mode 100644 mobile/src/main/res/layout/fragment_main.xml create mode 100644 mobile/src/main/res/layout/newmain.xml delete mode 100644 mobile/src/main/res/values-v21/styles.xml delete mode 100644 mobile/src/main/res/values-w820dp/dimens.xml create mode 100644 mobile/src/main/res/xml/pref_frag_main.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c50ab7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.iml +.gradle +/local.properties +.DS_Store +/build +/captures diff --git a/build.gradle b/build.gradle index e0b366a..aff4f41 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/mobile/build.gradle b/mobile/build.gradle index 94efc61..6a9f4ee 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -2,11 +2,11 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { applicationId "com.acmetensortoys.android.teled" - minSdkVersion 19 + minSdkVersion 23 targetSdkVersion 23 versionCode 1 versionName "1.0" @@ -22,6 +22,14 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } + + dataBinding { enabled = true } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + wearApp project(':wear') + testCompile 'junit:junit:4.12' } // IOIO support @@ -32,11 +40,23 @@ dependencies { compile 'com.github.ytai.ioio:IOIOLibAndroidDevice:5.07' } +// Functional java lets us not invent our own names for things that any +// respectable language ought to have had from the start. ;) dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - wearApp project(':wear') - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.google.android.gms:play-services:+' - compile 'com.android.support:design:23.1.1' + compile 'org.functionaljava:functionaljava:4.5' + // compile 'org.functionaljava:functionaljava-java-core:4.5' +} + +// Datastructures library +dependencies { + compile group: 'com.google.guava', name: 'guava', version: '19.0' } + +// Additional Google-isms +dependencies { + compile 'com.android.support:appcompat-v7:+' + compile 'com.android.support:design:+' + compile 'com.android.support:support-v4:23.+' + compile 'com.google.android.gms:play-services-location:9.0.2' +} + diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 8dbc759..9175def 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -2,12 +2,18 @@ - - - - - + + + + + + + + + + + + android:name=".UI.MainActivity" + android:label="@string/app_name"> + - - + + + - + \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactories.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactories.java new file mode 100644 index 0000000..3fac1f8 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactories.java @@ -0,0 +1,133 @@ +package com.acmetensortoys.android.teled.IOIO; + +import java.util.Collection; + +import fj.function.Effect1; +import fj.Void; + +import ioio.lib.api.AnalogInput; +import ioio.lib.api.Closeable; +import ioio.lib.api.DigitalOutput; +import ioio.lib.api.IOIO; +import ioio.lib.api.PwmOutput; + +public class BehaviorFactories { + static private BehaviorFactory makeBlinkOnce(final int pinId, final int dur) { + return new BehaviorFactory() { + public Behavior create(IOIO ioio_, Collection cc) + throws Exception { + final DigitalOutput pin = ioio_.openDigitalOutput(pinId); + cc.add(pin); + return new Behavior() { + public void run() throws Exception { + pin.write(false); + Thread.sleep(dur); + pin.write(true); + } + public void updateIn(Void v) { v.absurd(); } + }; + } + }; + } + static public BehaviorFactory makeBlinkLEDOnce(int dur) { + return makeBlinkOnce(IOIO.LED_PIN, dur); + } + + static private BehaviorFactory makeBlinkMany(final int pinId, final int dur) { + return new BehaviorFactory() { + public Behavior create(IOIO ioio_, Collection cc) + throws Exception { + final DigitalOutput pin = ioio_.openDigitalOutput(pinId); + cc.add(pin); + return new Behavior() { + public void run() throws Exception { + while(!Thread.currentThread().isInterrupted()) { + pin.write(false); + Thread.sleep(dur); + pin.write(true); + Thread.sleep(dur/2); + } + } + public void updateIn(Void v) { v.absurd(); } + }; + } + }; + } + static public BehaviorFactory makeBlinkLEDMany(int dur) { + return makeBlinkMany(IOIO.LED_PIN, dur); + } + + static public BehaviorFactory makePWMListener() { + return new BehaviorFactory() { + public Behavior create(IOIO ioio_, Collection cc) throws Exception { + final DigitalOutput motorEnable = ioio_.openDigitalOutput(1, false); // active low + cc.add(motorEnable); + final PwmOutput motorPwm = ioio_.openPwmOutput(2, 100); + cc.add(motorPwm); + return new Behavior() { + private float v; + + public void run() throws Exception { + while(!Thread.currentThread().isInterrupted()) { + this.wait(); + motorPwm.setDutyCycle(v); + } + } + + public void updateIn(Float v) { + this.v = v; + this.notify(); + } + }; + } + }; + } + + static public BehaviorFactory makeForceStreamer(final Effect1 cb) { + return new BehaviorFactory() { + public Behavior create(IOIO ioio_, Collection cc) throws Exception { + final AnalogInput sensor = ioio_.openAnalogInput(31); + cc.add(sensor); + sensor.setBuffer(50); + return new Behavior() { + public void run() throws Exception { + while (!Thread.currentThread().isInterrupted()) { + float v = sensor.getVoltageBuffered(); + cb.f((double)v); + } + } + public void updateIn(Void v) { v.absurd(); } + }; + } + }; + } + + static public BehaviorFactory makeForceFeedback(final Effect1 cb) { + return new BehaviorFactory() { + public Behavior create(IOIO ioio_, Collection cc) throws Exception { + final AnalogInput sensor = ioio_.openAnalogInput(31); + cc.add(sensor); + sensor.setBuffer(50); + final DigitalOutput motorEnable = ioio_.openDigitalOutput(1, false); // active low + cc.add(motorEnable); + final PwmOutput motorPwm = ioio_.openPwmOutput(2, 100); + cc.add(motorPwm); + return new Behavior() { + public void run() throws Exception { + float setv = 0.0f; + while (!Thread.currentThread().isInterrupted()) { + float v = sensor.getVoltageBuffered(); + // Try to save some bandwidth by only updating if it matters. + if (Math.abs(v - setv) > 0.1) { + motorPwm.setDutyCycle(v / 3.3f); + setv = v; + } + cb.f((double)v); + } + } + public void updateIn(Void v) { v.absurd(); } + }; + } + }; + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactory.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactory.java new file mode 100644 index 0000000..59957d3 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/BehaviorFactory.java @@ -0,0 +1,25 @@ +package com.acmetensortoys.android.teled.IOIO; + +import java.util.Collection; + +import ioio.lib.api.Closeable; +import ioio.lib.api.IOIO; + +public interface BehaviorFactory { + abstract class Behavior { + @SuppressWarnings("EmptyMethod") + public void shutdown() { } + abstract public void updateIn(In i); + abstract public void run() throws Exception; + } + + /* + The provided set of Closeable things is initially empty but can be + added to to simplify resource management inside the create() method and + in the returned Behavior: if create() raises an exception, the contents + of cc will be iterated and closed. When it comes time to shut down the + behavior, again, cc will be iterated and closed. Hooray mutation, I + guess; the cause of, and solution to, all of mutations' problems. + */ + Behavior create(IOIO i, Collection cc) throws Exception; +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/TeleDIOIOManager.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/TeleDIOIOManager.java new file mode 100644 index 0000000..885b331 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIO/TeleDIOIOManager.java @@ -0,0 +1,121 @@ +// Based, in long past, on IOIOService example from Ytai's repository + +package com.acmetensortoys.android.teled.IOIO; + +import android.support.annotation.Nullable; +import android.util.Log; + +import com.acmetensortoys.android.teled.Utils.BehaviorHandle; +import com.acmetensortoys.android.teled.Utils.ThreadBehaviorHandle; +import com.acmetensortoys.android.teled.Utils.Subscribee; +import com.acmetensortoys.android.teled.Utils.SubscribeeImpl; + +import java.util.ArrayList; + +import fj.function.Effect1; + +import ioio.lib.api.Closeable; +import ioio.lib.api.IOIO; +import ioio.lib.api.exception.ConnectionLostException; +import ioio.lib.util.BaseIOIOLooper; +import ioio.lib.util.IOIOLooper; + +public class TeleDIOIOManager + implements ioio.lib.util.IOIOLooperProvider { + private IOIO ioio = null; // If we have one connected, here 'tis. + + // TODO We should have some state that lets us identify which IOIO we mean + // which should be configurable through the UI. + + // Subscribe or unsubscribe to the IOIO being connected; + // when we are ready, the behaviors offered below will work; when + // not, they won't. On transition from ready to unready, all the + // threads will die of their own accord as their closed-over + // objects which reach into the IOIO issue exceptions. + private SubscribeeImpl ioioStatus = new SubscribeeImpl<>(false); + public Subscribee getStatus() { return ioioStatus; } + + @Override + public IOIOLooper createIOIOLooper(final String type, Object info) { + if (type.equals("ioio.lib.android.bluetooth.BluetoothIOIOConnection")) { + Object[] info_ = (Object[]) info; + Log.d("TDIS", "Looper creating for " + info_[0].toString() + ":" + info_[1].toString()); + } + + // TODO Check to see if it is the right IOIO. + + return new BaseIOIOLooper() { + @Override + protected void setup() throws ConnectionLostException, + InterruptedException { + synchronized (TeleDIOIOManager.this) { + if (ioio != null) { + throw new RuntimeException("Aaaaaaa! Setup with existing IOIO?"); + } + ioio = ioio_; + ioioStatus.publish(true); + } + } + + // All action takes place in the worker threads which are spawned below. + // So here we just park ourselves in a waiter and hope for the best. + @Override + public void loop() throws ConnectionLostException, + InterruptedException { + ioio.waitForDisconnect(); + } + + @Override + public void disconnected() { + synchronized (TeleDIOIOManager.this) { + ioioStatus.publish(false); + TeleDIOIOManager.this.ioio = null; + } + } + }; + } + + @Nullable + public BehaviorHandle makeBehaviorThread(BehaviorFactory bf) { + IOIO ioio_; + synchronized (TeleDIOIOManager.this) { + ioio_ = ioio; + } + if (ioio_ == null) { + return null; + } + final BehaviorFactory.Behavior b; + final ArrayList cc = new ArrayList<>(5); + try { + b = bf.create(ioio_, cc); + } catch (Exception e) { + for (Closeable c : cc) { c.close(); } + return null; + } + final SubscribeeImpl blni + = new SubscribeeImpl<>(BehaviorHandle.BehaviorState.NEW); + return new ThreadBehaviorHandle<>(new Thread() { + public void run() { + try { + blni.publish(BehaviorHandle.BehaviorState.RUNNING); + b.run(); + } catch (Exception e) { + // ignored; the thread is allowed to just die; + // anyone curious can use .isAlive() or .join(). + // They're also welcome to .interrupt() + Log.e("IOIOBehavior", "Exiting from exception", e); + } finally { + b.shutdown(); + for (Closeable c : cc) { c.close(); } + // Publish DEAD state after all resources have been released, + // in case one of our subscribers wants to claim them. + blni.publish(BehaviorHandle.BehaviorState.DEAD); + } + } + }, new Effect1() { + public void f(In x) { + b.updateIn(x); + } + }, blni); + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java deleted file mode 100644 index 2293ae3..0000000 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.acmetensortoys.android.teled; - -import java.util.Collection; - -import ioio.lib.api.AnalogInput; -import ioio.lib.api.Closeable; -import ioio.lib.api.DigitalOutput; -import ioio.lib.api.IOIO; -import ioio.lib.api.PwmOutput; - -public class IOIOBehaviors { - static abstract class Factory { - abstract class Behavior { - @SuppressWarnings("EmptyMethod") - void shutdown() { } - abstract void run() throws Exception; - } - /* - The provided set of Closeable things is initially empty but can be - added to to simplify resource management inside the create() method and - in the returned Behavior: if create() raises an exception, the contents - of cc will be iterated and closed. When it comes time to shut down the - behavior, again, cc will be iterated and closed. Hooray mutation, I - guess; the cause of, and solution to, all of mutations' problems. - */ - abstract Behavior create(IOIO i, Collection cc) throws Exception; - } - - static private Factory makeBlinkOnce(final int pinId, final int dur) { - return new Factory() { - public Behavior create(IOIO ioio_, Collection cc) - throws Exception { - final DigitalOutput pin = ioio_.openDigitalOutput(pinId); - cc.add(pin); - return new Behavior() { - public void run() throws Exception { - pin.write(false); - Thread.sleep(dur); - pin.write(true); - } - }; - } - }; - } - static public Factory makeBlinkLEDOnce(int dur) { - return makeBlinkOnce(IOIO.LED_PIN, dur); - } - - static private Factory makeBlinkMany(final int pinId, final int dur) { - return new Factory() { - public Behavior create(IOIO ioio_, Collection cc) - throws Exception { - final DigitalOutput pin = ioio_.openDigitalOutput(pinId); - cc.add(pin); - return new Behavior() { - public void run() throws Exception { - while(true) { - pin.write(false); - Thread.sleep(dur); - pin.write(true); - Thread.sleep(dur/2); - } - } - }; - } - }; - } - static public Factory makeBlinkLEDMany(int dur) { - return makeBlinkMany(IOIO.LED_PIN, dur); - } - - public interface ForceFeedbackCallback { - void run(float v); - } - static public Factory makeForceFeedback(final ForceFeedbackCallback cb) { - return new Factory() { - public Behavior create(IOIO ioio_, Collection cc) throws Exception { - final AnalogInput sensor = ioio_.openAnalogInput(31); - cc.add(sensor); - sensor.setBuffer(50); - final DigitalOutput motorEnable = ioio_.openDigitalOutput(1, false); // active low - cc.add(motorEnable); - final PwmOutput motorPwm = ioio_.openPwmOutput(2, 100); - cc.add(motorPwm); - return new Behavior() { - public void run() throws Exception { - float setv = 0.0f; - while (!Thread.interrupted()) { - float v = sensor.getVoltageBuffered(); - // Try to save some bandwidth by only updating if it matters. - if (Math.abs(v - setv) > 0.1) { - motorPwm.setDutyCycle(v / 3.3f); - setv = v; - } - // Hooray for JITs; I hope they can code-motion this test out. - if (cb != null) { - cb.run(v); - } - } - } - }; - } - }; - } -} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java deleted file mode 100644 index 0ef8c85..0000000 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java +++ /dev/null @@ -1,143 +0,0 @@ -// Based, in long past, on IOIOService example from Ytai's repository - -package com.acmetensortoys.android.teled; - -import android.app.Notification; -import android.content.Intent; -import android.os.IBinder; -import android.os.Binder; -import android.support.annotation.Nullable; -import android.util.Log; -import java.util.Vector; - -import ioio.lib.api.AnalogInput; -import ioio.lib.api.Closeable; -import ioio.lib.api.DigitalOutput; -import ioio.lib.api.IOIO; -import ioio.lib.api.PwmOutput; -import ioio.lib.api.exception.ConnectionLostException; -import ioio.lib.util.BaseIOIOLooper; -import ioio.lib.util.IOIOLooper; - -public class IOIOService extends ioio.lib.util.android.IOIOService { - private static final int NOTIFY_ID = 1; - private IOIO ioio = null; // If we have one connected, here 'tis. - // TODO We should have some state that lets us identify which IOIO we mean - // which should be configurable through the UI. - - public interface OnReady { - void onReady(); - void onUnready(); - } - private Vector subscribers = new Vector<>(5); - - @Override - public IOIOLooper createIOIOLooper(final String type, Object info) { - if (type.equals("ioio.lib.android.bluetooth.BluetoothIOIOConnection")) { - Object[] info_ = (Object[])info; - Log.d("TDIS", "Looper creating for " + info_[0].toString() + ":" + info_[1].toString()); - } - - // TODO Check to see if it is the right IOIO. - - return new BaseIOIOLooper() { - @Override - protected void setup() throws ConnectionLostException, - InterruptedException { - synchronized(IOIOService.this) { - if(ioio != null) { - Log.e("TDIS", "Aaaaaaa! Setup with existing IOIO?", new Exception()); - } - ioio = ioio_; - for (OnReady l : subscribers) { l.onReady(); } - } - } - - @Override - public void loop() throws ConnectionLostException, - InterruptedException { - ioio.waitForDisconnect(); - } - - @Override - public void disconnected() { - synchronized(IOIOService.this) { - for (OnReady l : subscribers) { l.onUnready(); } - IOIOService.this.ioio = null; - } - } - }; - } - - final class LocalBinder extends Binder { - // XXX for development only - // IOIOService getService() { return IOIOService.this; } - - // Subscribe or unsubscribe to the IOIO being connected; - // when we are ready, the behaviors offered below will work; when - // not, they won't. On transition from ready to unready, all the - // threads will die of their own accord as their closed-over - // objects which reach into the IOIO issue exceptions. - public void addOnReady(OnReady l) { - subscribers.add(l); - } - public void removeOnReady(OnReady l) { - subscribers.remove(l); - } - - @Nullable - public Thread makeBehaviorThread(IOIOBehaviors.Factory bf) { - IOIO ioio_; - // XXX? synchronized(IOIOService.this) { - ioio_ = ioio; - // } - if(ioio_ == null) { - return null; - } - final IOIOBehaviors.Factory.Behavior b; - final Vector cc = new Vector<>(5); - try { - b = bf.create(ioio_, cc); - } catch (Exception e) { - for(Closeable c : cc) { c.close(); } - return null; - } - return new Thread() { - public void run() { - try { - b.run(); - } catch (Exception e) { - // ignored; the thread is allowed to just die; - // anyone curious can use .isAlive() or .join(). - // They're also welcome to .interrupt() - } finally { - b.shutdown(); - for(Closeable c : cc) { c.close(); } - } - } - }; - } - - } - private final IBinder mBinder = new LocalBinder(); - - @Override - public IBinder onBind(Intent arg0) { - Log.d("TDIS", "onBind"); - Notification.Builder nb = new Notification.Builder(this); - Notification n = nb.setSmallIcon(R.drawable.ic_menu_send) - .setContentTitle("TeleD IOIO service running") - .setWhen(System.currentTimeMillis()) - .setOngoing(true) - .build(); - startForeground(NOTIFY_ID, n); - return mBinder; - } - - @Override - public boolean onUnbind(Intent arg0) { - stopForeground(true); - stopSelf(); - return false; - } -} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java b/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java deleted file mode 100644 index 2f9109e..0000000 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.acmetensortoys.android.teled; - -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.ComponentName; -import android.os.IBinder; -import android.os.Bundle; -import android.renderscript.RSInvalidStateException; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.view.View; -import android.support.design.widget.NavigationView; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; -import android.util.Log; - -public class MainActivity extends AppCompatActivity - implements NavigationView.OnNavigationItemSelectedListener { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); - fab.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } - }); - - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( - this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); - drawer.setDrawerListener(toggle); - toggle.syncState(); - - NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); - navigationView.setNavigationItemSelectedListener(this); - - Intent i = new Intent(this, IOIOService.class); - startService(i); // start the IOIO service - bindService(i, tdisSC, Context.BIND_AUTO_CREATE); // and get a handle to it, eventually - } - - @Override - public void onBackPressed() { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - if (drawer.isDrawerOpen(GravityCompat.START)) { - drawer.closeDrawer(GravityCompat.START); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_scrolling, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle action bar item clicks here. The action bar will - // automatically handle clicks on the Home/Up button, so long - // as you specify a parent activity in AndroidManifest.xml. - int id = item.getItemId(); - - //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - return true; - } - - return super.onOptionsItemSelected(item); - } - - private Thread blinker; - private void makeBlinker() { - blinker = tdis.makeBehaviorThread(IOIOBehaviors.makeBlinkLEDMany(500)); - if(blinker != null) { - blinker.start(); - } else { - Log.d("Main", "onReady unable to start blinker"); - } - } - - private IOIOService.LocalBinder tdis = null; - private IOIOService.OnReady tdisOnReady = new IOIOService.OnReady() { - public void onReady() { - Log.d("Main", "TDIS onReady"); - if(blinker != null) { - throw new RSInvalidStateException("onReady with blinker"); - } - makeBlinker(); - } - public void onUnready() { - Log.d("Main", "TDIS onUnready"); - blinker = null; - } - }; - private final ServiceConnection tdisSC = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName cn, IBinder service) { - Log.d("Main", "TDIS conn"); - tdis = ((IOIOService.LocalBinder) service); - tdis.addOnReady(tdisOnReady); - } - @Override - public void onServiceDisconnected(ComponentName cn) { - // Because we're binding to a local service, this should never happen to us - Log.d("Main", "TDIS discon"); - tdis = null; - tdisOnReady.onUnready(); - } - }; - - @SuppressWarnings("StatementWithEmptyBody") - @Override - public boolean onNavigationItemSelected(MenuItem item) { - // Handle navigation view item clicks here. - int id = item.getItemId(); - - if (id == R.id.nav_camera) { - // Handle the camera action - } else if (id == R.id.nav_gallery) { - - } else if (id == R.id.nav_slideshow) { - - } else if (id == R.id.nav_manage) { - - } else if (id == R.id.nav_share) { - if(tdis != null) { - Log.d("Main", "Share w/ non-null..."); - if(blinker == null) { - makeBlinker(); - } else { - blinker.interrupt(); - blinker = null; - } - } - } else if (id == R.id.nav_send) { - Log.d("Main", "Send..."); - } - - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); - drawer.closeDrawer(GravityCompat.START); - return true; - } - - @Override - public void onDestroy(){ - unbindService(tdisSC); - super.onDestroy(); - } -} - -/* TODO: - * - * * A settings activity with SMS configuration, including authentication - * information and an enable button. - * - * * A settings activity with Bluetooth configuration and maybe USB? - * - * * Spawn the IOIO service on startup if it isn't and see it work. - */ diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java deleted file mode 100644 index a964915..0000000 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.acmetensortoys.android.teled; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.IBinder; -import android.util.Log; - -import java.util.Vector; - -public class RemoteControlService extends Service { - private boolean remoteControlEnabled = false; - private Vector ongoingRemoteBehaviors = new Vector<>(1); - - private void removeOngoing(Thread t) { - synchronized(RemoteControlService.this) { - ongoingRemoteBehaviors.remove(t); - } - } - - public interface OnCessation { - void run(); - } - - // Assumes that t has been started already - private void _addOngoing(final Thread t, final OnCessation l) { - Thread nt = new Thread() { - public void run() { - while(t.isAlive()) { - //noinspection EmptyCatchBlock - try { t.join(); } catch (InterruptedException e) { } - } - removeOngoing(t); - l.run(); - } - }; - nt.start(); - synchronized(this) { ongoingRemoteBehaviors.add(t); } - } - - private boolean startAndAddOngoing(Thread t, OnCessation l) { - if(!remoteControlEnabled) { - return false; - } - t.start(); - _addOngoing(t, l); - return true; - } - - // TODO Should have a similar set of functionality for behaviors that should - // resume at some later point. Consider losing the IOIO connection but wanting - // to restart when it comes back. - - class LocalBinder extends Binder { - public void stopRemoteControl() { - synchronized (this) { - remoteControlEnabled = false; - // Cause all outstanding behaviors to cease their activities - for (Thread t : ongoingRemoteBehaviors) { - t.interrupt(); - } - } - } - } - private final IBinder mBinder = new LocalBinder(); - - @Override - public IBinder onBind(Intent arg0) { - Log.d("RCS", "onBind"); - return mBinder; - } - - @Override - public boolean onUnbind(Intent arg0) { - Log.d("RCS", "onUnbind"); - return false; - } -} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java b/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java deleted file mode 100644 index 4a4b5b0..0000000 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.acmetensortoys.android.teled; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class SMSRecv extends BroadcastReceiver { - public SMSRecv() { - } - - @Override - public void onReceive(Context context, Intent intent) { - // TODO: This method is called when the BroadcastReceiver is receiving - // an Intent broadcast. - throw new UnsupportedOperationException("Not yet implemented"); - - /* NWF planning aloud here. - * - * We should check that we should even be paying attention to SMS; - * they may be disabled. - * - * We're going to check some authentication present in the incoming - * message -- source number, signed message hash, etc. -- and are then - * going to decode it in full. - * - * The message should contain a sequence number that we echo back, - * Since SMS's may reorder on the wire, let's just not support - * multiple outstanding messages; simpler on us this way... - */ - } -} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Service/EphemeralTeleDService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/EphemeralTeleDService.java new file mode 100644 index 0000000..ba052fc --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/EphemeralTeleDService.java @@ -0,0 +1,67 @@ +package com.acmetensortoys.android.teled.Service; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +/** + * An {@link IntentService} subclass for handling asynchronous task requests in + * a service on a separate handler thread. + * + * Used within TeleD to handle things like SMS dispatch via PendingIntent. + * What a pain in the ass. + */ +public class EphemeralTeleDService extends IntentService { + private static final String ACTION_SEND_LOCATION_SMS + = "com.acmetensortoys.android.teled.action.SEND_LOCATION_SMS"; + private static final String ACTION_TEST + = "com.acmetensortoys.android.teled.action.TEST"; + + // TODO: Rename parameters + private static final String EXTRA_PARAM1 = "com.acmetensortoys.android.teled.extra.PARAM1"; + private static final String EXTRA_PARAM2 = "com.acmetensortoys.android.teled.extra.PARAM2"; + + public EphemeralTeleDService() { + super("EphemeralTeleDService"); + } + + public static PendingIntent pendingSendLocationSMS(Context context, Uri data) { + Intent intent = new Intent(ACTION_SEND_LOCATION_SMS, + data, context, EphemeralTeleDService.class); + return PendingIntent.getService(context, 0, intent, 0); + } + + public static PendingIntent pendingTest(Context context, Uri data) { + Intent intent = new Intent(ACTION_TEST, data, context, EphemeralTeleDService.class); + return PendingIntent.getService(context, 0, intent, 0); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (intent != null) { + final String action = intent.getAction(); + if (ACTION_TEST.equals(action)) { + Log.d("EphemeralTeleDService", "Test action: " + intent.toString() + " eb=" + intent.getExtras().toString()); + } else if (ACTION_SEND_LOCATION_SMS.equals(action)) { + // final String param1 = intent.getStringExtra(EXTRA_PARAM1); + handleActionSendLocationSMS(intent); + } /* else if (ACTION_BAZ.equals(action)) { + final String param1 = intent.getStringExtra(EXTRA_PARAM1); + final String param2 = intent.getStringExtra(EXTRA_PARAM2); + handleActionBaz(param1, param2); + } */ + } + } + + /** + * Handle action Foo in the provided background thread with the provided + * parameters. + */ + private void handleActionSendLocationSMS(Intent i) { + // TODO: Handle action Foo + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Service/PositionReporting.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/PositionReporting.java new file mode 100644 index 0000000..4be09a9 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/PositionReporting.java @@ -0,0 +1,18 @@ +package com.acmetensortoys.android.teled.Service; + +import android.Manifest; +import android.content.Context; +import android.location.LocationListener; +import android.location.LocationManager; + +public abstract class PositionReporting +{ + public PositionReporting(Context ctx, LocationListener ll) { + LocationManager lm = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + + ctx.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, + "Missing location permission"); + + lm.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 5000, 10, ll); + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Service/SMSRecv.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/SMSRecv.java new file mode 100644 index 0000000..04841d2 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/SMSRecv.java @@ -0,0 +1,65 @@ +package com.acmetensortoys.android.teled.Service; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import com.acmetensortoys.android.teled.Utils.BehaviorHandleSet; + +// TODO: The constructor of this class should bind the Service for us +// so we're not binding and un-binding at every message? What a pain in the ass. + +public class SMSRecv extends BroadcastReceiver { + public SMSRecv() { + Log.d("SMSRecv","Constructor"); + } + + @Override + public void onReceive(final Context context, Intent intent) { + Log.d("TeleD/SMSRecv", intent.toString()); + + // TODO: Check that we should be doing SMS, authenticate incoming message, + // figure out what behaviors need to be spawned, ... + + context.bindService( + new Intent(context, TeleDService.class), + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + TeleDService.LocalBinder tds = ((TeleDService.LocalBinder) service); + + // Grab list of current behaviors + StringBuilder sb = new StringBuilder(); + BehaviorHandleSet.showMetaMap(sb, tds.getActiveBehaviors()); + + // TODO: Send response message + + context.unbindService(this); + } + + @Override + public void onServiceDisconnected(ComponentName name) { ; } + }, + 0); + + /* NWF planning aloud here. + * + * We should check that we should even be paying attention to SMS; + * they may be disabled. That's now handled by dynamic registration of + * the receiver in TeleDService. + * + * We're going to check some authentication present in the incoming + * message -- source number, signed message hash, etc. -- and are then + * going to decode it in full. + * + * The message should contain a sequence number that we echo back, + * Since SMS's may reorder on the wire, let's just not support + * multiple outstanding messages; simpler on us this way... + */ + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Service/TeleDService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/TeleDService.java new file mode 100644 index 0000000..6a25be7 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Service/TeleDService.java @@ -0,0 +1,202 @@ +package com.acmetensortoys.android.teled.Service; + +import android.Manifest; +import android.app.Notification; +import android.app.Service; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.acmetensortoys.android.teled.IOIO.BehaviorFactory; +import com.acmetensortoys.android.teled.IOIO.TeleDIOIOManager; +import com.acmetensortoys.android.teled.R; +import com.acmetensortoys.android.teled.Utils.BehaviorHandle; +import com.acmetensortoys.android.teled.Utils.BehaviorHandleSet; +import com.acmetensortoys.android.teled.Utils.Subscribee; +import com.acmetensortoys.android.teled.Utils.SubscribeeImpl; +import com.acmetensortoys.android.teled.Utils.SuspendableBH; + +import java.util.Map; + +import fj.function.Effect1; +import ioio.lib.util.android.IOIOAndroidApplicationHelper; + +/* + * This is the core persistent object of the system (or at least, that's the idea); + * this service should know how to respond to messages from the UI, + * any remote control channels, location sources, and the underlying + * behavioral hardware. Phfew! + */ +public class TeleDService extends Service { + // Manage a collection of behaviors + private final BehaviorHandleSet> bhs = new BehaviorHandleSet(); + + // Some behaviors pause and unpause on SMS preference or GPS preference (or both!) + private final BehaviorHandleSet> smsbhs = new BehaviorHandleSet<>(); + private final BehaviorHandleSet> gpsbhs = new BehaviorHandleSet<>(); + + // Manage an IOIO + private final TeleDIOIOManager ios = new TeleDIOIOManager(); + private final IOIOAndroidApplicationHelper ioh = new IOIOAndroidApplicationHelper(this, ios); + private boolean ioactive; + private void enableIOIO(Boolean on) { + synchronized(ioh) { + if (on && !ioactive) { + ioh.restart(); + } else if (!on && ioactive) { + ioh.stop(); + } + ioactive = on; + } + } + private void enableIOIOByPreference() { + enableSMS(PreferenceManager + .getDefaultSharedPreferences(this) + .getBoolean("ioio_ena", false)); + } + + // Manage a Broadcast Receiver for SMSes + // + // XXX Should this really be here or should it be part of the main application? + // it's moved back and forth a few times. + private final SubscribeeImpl mSMSStatSubee = new SubscribeeImpl<>(false); + private final SMSRecv mSMS = new SMSRecv(); + private void enableSMS(Boolean on) { + synchronized(mSMS) { + if (on && !mSMSStatSubee.getLast()) { + mSMSStatSubee.publish(true); + registerReceiver(mSMS, new IntentFilter(Manifest.permission.RECEIVE_SMS)); + } else if (!on && mSMSStatSubee.getLast()) { + unregisterReceiver(mSMS); + mSMSStatSubee.publish(false); + } + } + } + private void enableSMSByPreference() { + enableSMS(PreferenceManager + .getDefaultSharedPreferences(this) + .getBoolean("sms_ena", false)); + } + + // Export an API + public class LocalBinder extends Binder { + public Subscribee getIOIOStatus() { return ios.getStatus(); } + public BehaviorHandle addIOIOBehavior( + BehaviorHandleSet.Metadata meta, + BehaviorFactory bf) { + return addActiveBehavior(meta, ios.makeBehaviorThread(bf)); + } + + public BehaviorHandle + addActiveBehavior(BehaviorHandleSet.Metadata meta, BehaviorHandle bh) { + bhs.addActiveBehavior(meta, bh); + return bh; + } + + public BehaviorHandle getActiveBehaviorById(int id) { + return bhs.getBehaviorById(id); + } + public Map + getActiveBehaviors() { + return bhs.getActiveBehaviorMeta(); + } + + // are both SMS and GPS enabled? + public boolean sendSMSPositionReports() { + return mSMSStatSubee.getLast() /* && mGPSStatSubee.getLast() */ ; + } + + // XXX Any need for this? + public Subscribee getSMSStatus() { return mSMSStatSubee; } + } + private final LocalBinder mBinder = new LocalBinder(); + + // Android preferences event glue + private final SharedPreferences.OnSharedPreferenceChangeListener ospcl = + new SharedPreferences.OnSharedPreferenceChangeListener() { + @Override + public void + onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (sharedPreferences != PreferenceManager + .getDefaultSharedPreferences(TeleDService.this)) { + // Some other collection of preferences changing? How odd. + return; + } + switch(key) { + case "ioio_ena": enableIOIOByPreference(); break; + case "sms_ena": enableSMSByPreference(); break; + } + } + }; + + // Android service-management glue + private static final int NOTIFY_ID = 1; + + @Override + public IBinder onBind(Intent arg0) { + Log.d("TDIS", "onBind"); + + // This is a little hackish; to simplify the start/stop/restart state machine, + // just always start and immediately disable if preferences indicate. + ioh.start(); + ioactive = true; + enableIOIOByPreference(); + + enableSMSByPreference(); + + PreferenceManager. + getDefaultSharedPreferences(this). + registerOnSharedPreferenceChangeListener(ospcl); + + Notification.Builder notif = new Notification.Builder(this); + notif.setSmallIcon(R.drawable.ic_menu_send) + .setContentTitle("TeleD service running") + .setWhen(System.currentTimeMillis()) + .setOngoing(true); + startForeground(NOTIFY_ID, notif.build()); + + // TODO Hook a callback for displaying SMS enabled status? + + // TODO Hook a callback to update notification contents + bhs.getActiveBehaviorSubee().subscribe(new Effect1>() { + public void f(Map x) { + StringBuilder sb = new StringBuilder("Update to active behaviors:\n"); + BehaviorHandleSet.showMetaMap(sb, x); + Log.d("TDIS", sb.toString()); + } + }); + + return mBinder; + } + + @Override + public boolean onUnbind(Intent arg0) { + PreferenceManager. + getDefaultSharedPreferences(this). + registerOnSharedPreferenceChangeListener(ospcl); + + enableSMS(false); + enableIOIO(false); + + stopForeground(true); + return false; + } + + @Override + public void onCreate() { + super.onCreate(); + ioh.create(); + } + + @Override + public void onDestroy() { + ioh.destroy(); + super.onDestroy(); + } + +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/UI/FragPrefs.java b/mobile/src/main/java/com/acmetensortoys/android/teled/UI/FragPrefs.java new file mode 100644 index 0000000..fc3cc15 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/UI/FragPrefs.java @@ -0,0 +1,65 @@ +package com.acmetensortoys.android.teled.UI; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.provider.ContactsContract; +import android.util.Log; + +import com.acmetensortoys.android.teled.R; + +public class FragPrefs + extends PreferenceFragment { + + private final int REQID_SMS_PICK = 1234; + + @Override + public void onCreate(Bundle sis) { + super.onCreate(sis); + addPreferencesFromResource(R.xml.pref_frag_main); + + /* Customize preferences */ + Preference p; + + /* + * Bahaha. We can't create a custom Preference class for this because... + * getPreferenceManager().getFragment() is package private. Goddammit. + */ + p = findPreference("sms_pick"); + p.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference _p) { + Intent conPik = new Intent( + Intent.ACTION_PICK, + ContactsContract.Contacts.CONTENT_URI); + startActivityForResult(conPik, REQID_SMS_PICK); + return true; + } + }); + + /* + p = findPreference("ioio_mac"); + p.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference _p) { + ListPreference lp = (ListPreference) _p; + lp.setEntries(new CharSequence[]{}); + lp.setEntryValues(new CharSequence[]{}); + return true; + } + }); + */ + } + + @Override + public void onActivityResult(int req, int res, Intent i) { + if(req == REQID_SMS_PICK && res == Activity.RESULT_OK) { + Log.d("FragPrefs", i.toString()); + } + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/UI/MainActivity.java b/mobile/src/main/java/com/acmetensortoys/android/teled/UI/MainActivity.java new file mode 100644 index 0000000..96a5563 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/UI/MainActivity.java @@ -0,0 +1,312 @@ +package com.acmetensortoys.android.teled.UI; + +import com.acmetensortoys.android.teled.R; +import com.acmetensortoys.android.teled.Service.EphemeralTeleDService; +import com.acmetensortoys.android.teled.Service.SMSRecv; +import com.acmetensortoys.android.teled.Utils.BehaviorHandle; +import com.acmetensortoys.android.teled.IOIO.BehaviorFactories; +import com.acmetensortoys.android.teled.Service.TeleDService; +import com.acmetensortoys.android.teled.Utils.BehaviorHandleSet; +import com.acmetensortoys.android.teled.Utils.SubscribeeImpl; + +import android.Manifest; +import android.app.Activity; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.ComponentName; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.Bundle; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.renderscript.RSInvalidStateException; +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; +import android.util.Log; + +import fj.function.Effect1; + +public class MainActivity extends Activity + implements SharedPreferences.OnSharedPreferenceChangeListener + /* + implements NavigationView.OnNavigationItemSelectedListener + */ +{ + + private final static int PERM_REQ_IX_RECV_SMS = 1; + private final static int PERM_REQ_IX_FINE_LOC = 2; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceManager + .getDefaultSharedPreferences(this) + .registerOnSharedPreferenceChangeListener(this); + + setContentView(R.layout.newmain); + + getFragmentManager().beginTransaction() + .add(R.id.main_fragment_container,new FragPrefs()) + .commit(); + + /* + setContentView(R.layout.fragment_main); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + */ + + /* + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + }); + + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.setDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + */ + } + + @Override + public void onDestroy(){ + if (tds != null) { unbindService(tdisSC); } + + PreferenceManager + .getDefaultSharedPreferences(this) + .unregisterOnSharedPreferenceChangeListener(this); + + super.onDestroy(); + } + + @Override + public void onStart() { + super.onStart(); + bindService(new Intent(this, TeleDService.class), tdisSC, + Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_scrolling, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + private BehaviorHandle blinker; + private void makeBlinker() { + blinker = tds.addIOIOBehavior( + new BehaviorHandleSet.Metadata("BlinkMany"), + BehaviorFactories.makeBlinkLEDMany(500)); + if(blinker != null) { + blinker.start(); + } else { + Log.d("Main", "unable to start blinker"); + } + } + + private TeleDService.LocalBinder tds = null; + private final Effect1 tdiOnReady = new Effect1() { + public void f(Boolean b) { + if(b) { + Log.d("Main", "TDI onReady"); + if (blinker != null) { + throw new RSInvalidStateException("onReady with blinker"); + } + makeBlinker(); + } else { + Log.d("Main", "TDI onUnready"); + blinker.interrupt(); + try { blinker.join(); } catch (InterruptedException e) { ; } + blinker = null; + } + } + }; + + private final ServiceConnection tdisSC = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName cn, IBinder service) { + Log.d("Main", "TDIS conn"); + tds = ((TeleDService.LocalBinder) service); + tds.getIOIOStatus().subscribe(tdiOnReady); + } + @Override + public void onServiceDisconnected(ComponentName cn) { + // Because we're binding to a local service, this should never happen to us + Log.d("Main", "TDIS discon"); + tds = null; + tdiOnReady.f(false); + } + }; + + /* + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (id == R.id.nav_camera) { + Log.d("Main", "Camera..."); + + if(tds != null) { + Map m = tds.getActiveBehaviors(); + for (Map.Entry x : m.entrySet()) { + BehaviorHandle b = tds.getActiveBehaviorById(x.getKey()); + if(b != null) { + Log.d("Main", "Stopping behavior ID " + x.getKey().toString()); + b.interrupt(); + try { b.join(); } catch (Exception e) { break; } + } + } + } + } else if (id == R.id.nav_gallery) { + if(tds != null) { + Log.d("Main", "Share w/ non-null..."); + if(blinker == null) { + makeBlinker(); + } else { + blinker.interrupt(); + blinker = null; + } + } + } else if (id == R.id.nav_slideshow) { + if(tds != null) { + } + } else if (id == R.id.nav_manage) { + if(tds != null) { + // tds.enableIOIO(true); + BehaviorHandle x = tds.addActiveBehavior("test", + ThreadBehaviorHandle.create(new Runnable() { + public void run() { + while(true) { + try { + Thread.sleep(5000); + } catch (Exception e) { + return; + } + Log.d("TestBehavior", "Tick"); + } + } + })); + x.start(); + } + } else if (id == R.id.nav_share) { + Log.d("Main", "Share..."); + + if(checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) + { + Log.d("Main", "Requesting permission?"); + requestPermissions( + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + PERM_REQ_IX_FINE_LOC); + } else { + Log.d("Main", "Have permission?"); + + LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); + Log.d("Main", lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER).toString()); + } + } else if (id == R.id.nav_send) { + Log.d("Main", "Send..."); + + if(checkSelfPermission(Manifest.permission.RECEIVE_SMS) + != PackageManager.PERMISSION_GRANTED) + { + Log.d("Main", "Requesting permission?"); + requestPermissions( + new String[]{Manifest.permission.RECEIVE_SMS}, + PERM_REQ_IX_RECV_SMS); + } else { + Log.d("Main", "Have permission?"); + if(tds != null) { + tds.enableSMS(true); + } + } + } + + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + */ + + @Override + public void onRequestPermissionsResult(int rq, @NonNull String[] perms, @NonNull int[] grs) { + Log.d("Main", "ORPR:" + perms[0] + ":" + Integer.toString(grs[0])); + //if(rq == PERM_REQ_IX_RECV_SMS && grs[0] == PackageManager.PERMISSION_GRANTED) { + //} + } + + // XXX Remove? + @Override + public void onSharedPreferenceChanged(SharedPreferences p, String k) { + switch(k) { + case "sms_ena": break; + case "gps_ena": break; + case "test": + AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); + PendingIntent pi = EphemeralTeleDService.pendingTest(this,Uri.EMPTY); + if(PreferenceManager + .getDefaultSharedPreferences(this) + .getBoolean("test", false)) { + Log.d("Main", "test OSPC enabled"); + am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime(), 1000 * 60, pi); + try { pi.send(); } catch (PendingIntent.CanceledException ce) { + Log.d("Main", "pi cancel?"); + } + } else { + Log.d("Main", "test OSPC disabled"); + am.cancel(pi); + pi.cancel(); + } + break; + default: + Log.d("MainActivity", "Unknown preference changed:" + k); + break; + } + } +} + +/* TODO: + * + * * A settings activity with SMS configuration, including authentication + * information and an enable button. + * + * * A settings activity with Bluetooth configuration and maybe USB? + * + * * Spawn the IOIO service on startup if it isn't and see it work. + */ diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/LocationUpdateBH.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/LocationUpdateBH.java new file mode 100644 index 0000000..9b1ae60 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/LocationUpdateBH.java @@ -0,0 +1,37 @@ +package com.acmetensortoys.android.teled.Utils.Android; + +import android.app.PendingIntent; +import android.content.Context; +import android.location.LocationManager; + +public class LocationUpdateBH extends PendingIntentBH { + private final Context c; + private final String prov; + private final long mint; + private final float mind; + + LocationUpdateBH(Context c, String provider, long minTime, float minDist, PendingIntent pi){ + super(pi); + this.c = c; + this.prov = provider; + this.mint = minTime; + this.mind = minDist; + } + + @Override + protected boolean onStart(PendingIntent pi) { + LocationManager lm = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE); + try { + lm.requestLocationUpdates(prov, mint, mind, pi); + return true; + } catch (SecurityException se) { + return false; + } + } + + @Override + protected void onPause(PendingIntent pi) { + LocationManager lm = (LocationManager) c.getSystemService(Context.LOCATION_SERVICE); + lm.removeUpdates(pi); + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/PendingIntentBH.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/PendingIntentBH.java new file mode 100644 index 0000000..fda15d9 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/PendingIntentBH.java @@ -0,0 +1,100 @@ +package com.acmetensortoys.android.teled.Utils.Android; + +import android.app.PendingIntent; + +import com.acmetensortoys.android.teled.Utils.Subscribee; +import com.acmetensortoys.android.teled.Utils.SubscribeeImpl; +import com.acmetensortoys.android.teled.Utils.SuspendableBH; + +import fj.Void; +import fj.function.Effect1; + +public abstract class PendingIntentBH extends SuspendableBH { + + // or restart; true to start, false to indicate a desire to stay suspended + abstract protected boolean onStart(PendingIntent pi); + + // or stop; must be idempotent + abstract protected void onPause(PendingIntent pi); + + public PendingIntentBH(PendingIntent pi) { + ln = new SubscribeeImpl<>(BehaviorState.NEW); + this.pi = pi; + } + + // + // You can stop reading here if all you want to know is what to extend. + // + + final private SubscribeeImpl ln; + final private PendingIntent pi; + + private void die() { + synchronized(this) { + ln.publish(BehaviorState.DEAD); + this.notifyAll(); + } + } + + @Override + public final void start() { + BehaviorState bs = ln.getLast(); + if(bs == BehaviorState.NEW || bs == BehaviorState.SUSPENDED) { + if(this.onStart(pi)) { + ln.publish(BehaviorState.RUNNING); + } else if (bs == BehaviorState.NEW) { + ln.publish(BehaviorState.SUSPENDED); + } + } + } + + @Override + public void suspend() { + BehaviorState bs = ln.getLast(); + switch(ln.getLast()) { + case RUNNING: + this.onPause(pi); + // fallthru + case NEW: + ln.publish(BehaviorState.SUSPENDED); + break; + case SUSPENDED: + case DEAD: + break; + } + } + + @Override + public final void interrupt() { + switch(ln.getLast()) { + case NEW: + case RUNNING: + this.onPause(pi); + case SUSPENDED: + die(); + break; + case DEAD: + break; + } + pi.cancel(); + } + + @Override + public final void join() throws InterruptedException { + synchronized (this) { + while (ln.getLast() != BehaviorState.DEAD) { + this.wait(); + } + } + } + + @Override + public final Subscribee getStateSubscribee() { + return ln; + } + + @Override + public final Effect1 getUpdateIn() { + return null; + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/RecurrentAlarmBH.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/RecurrentAlarmBH.java new file mode 100644 index 0000000..f2d83df --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Android/RecurrentAlarmBH.java @@ -0,0 +1,33 @@ +package com.acmetensortoys.android.teled.Utils.Android; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; + +public class RecurrentAlarmBH extends PendingIntentBH { + private final Context c; + private final int type; + private final long tam; + private final long ri; + + RecurrentAlarmBH(Context c, int type, long triggerAtMillis, long repeatInterval, PendingIntent pi){ + super(pi); + this.c = c; + this.type = type; + this.tam = triggerAtMillis; + this.ri = repeatInterval; + } + + @Override + protected boolean onStart(PendingIntent pi) { + AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE); + am.setRepeating(type, tam, ri, pi); + return true; + } + + @Override + protected void onPause(PendingIntent pi) { + AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE); + am.cancel(pi); + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandle.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandle.java new file mode 100644 index 0000000..6e923da --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandle.java @@ -0,0 +1,14 @@ +package com.acmetensortoys.android.teled.Utils; + +import fj.function.Effect1; + +public abstract class BehaviorHandle { + public enum BehaviorState { NEW, RUNNING, SUSPENDED, DEAD }; + + abstract public void start(); // move from NEW to RUNNING + abstract public void interrupt(); // request RUNNING to DEAD transition + abstract public void join() throws InterruptedException; // await status DEAD + abstract public Subscribee getStateSubscribee(); // to be notified on any state transition + + abstract public Effect1 getUpdateIn(); +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandleSet.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandleSet.java new file mode 100644 index 0000000..3282c83 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/BehaviorHandleSet.java @@ -0,0 +1,90 @@ +package com.acmetensortoys.android.teled.Utils; + +import android.graphics.drawable.Icon; + +import com.google.common.collect.HashBiMap; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import fj.function.Effect1; + +// Manage a collection of active behavior threads; removal from the collection +// is handled by the behaviors themselves when they transition. +// +// Each behavior is also given a unique integer identifier for remote reference. +public class BehaviorHandleSet> { + public static class Metadata { + final public String name; + final public Icon icon; + + public Metadata (String name) { + this.name = name; + this.icon = null; + } + public Metadata (String name, Icon icon) { + this.name = name; + this.icon = icon; + } + } + + private int nextId = 0; + private final HashBiMap activeBehaviorIds = HashBiMap.create(); + private final HashMap activeBehaviorMeta = new HashMap<>(); + private final SubscribeeImpl> abmSubee + = new SubscribeeImpl>(activeBehaviorMeta); + + public void + addActiveBehavior(final Metadata meta, final BH bh) + { + synchronized(this) { + while (activeBehaviorIds.containsValue(nextId)) { nextId++; } + activeBehaviorIds.put(bh, nextId); + activeBehaviorMeta.put(nextId, meta); + abmSubee.publish(getActiveBehaviorMeta()); + bh.getStateSubscribee().subscribe(new Effect1() { + public void f(BehaviorHandle.BehaviorState x) { + if (x == BehaviorHandle.BehaviorState.DEAD) { + synchronized(this) { + int oldId = activeBehaviorIds.remove(bh); + activeBehaviorMeta.remove(oldId); + abmSubee.publish(getActiveBehaviorMeta()); + } + } + } + }); + } + } + + public BH + getBehaviorById(int id) { + synchronized(this) { return activeBehaviorIds.inverse().get(id); } + } + + public Iterator + getBehaviors() { + return activeBehaviorIds.keySet().iterator(); + } + + // Clone the behavior meta map for consumption of the outside world. + @SuppressWarnings("unchecked") + public Map + getActiveBehaviorMeta() { + synchronized (this) { + return (Map)(activeBehaviorMeta.clone()); + } + } + + public Subscribee> + getActiveBehaviorSubee() { return abmSubee; } + + public static void showMetaMap(StringBuilder b, Map m) { + for(Map.Entry x : m.entrySet()) { + b.append(x.getKey()); + b.append(" "); + b.append(x.getValue().name); + b.append("\n"); + } + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Subscribee.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Subscribee.java new file mode 100644 index 0000000..5e97856 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/Subscribee.java @@ -0,0 +1,9 @@ +package com.acmetensortoys.android.teled.Utils; + +import fj.function.Effect1; + +public interface Subscribee { + V getLast(); + void subscribe(Effect1 cb); + void unsubscribe(Effect1 cb); +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SubscribeeImpl.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SubscribeeImpl.java new file mode 100644 index 0000000..9c8858c --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SubscribeeImpl.java @@ -0,0 +1,34 @@ +package com.acmetensortoys.android.teled.Utils; + +import java.util.LinkedList; +import java.util.List; +import fj.function.Effect1; + +public class SubscribeeImpl implements Subscribee { + private V last; + private final List> subscribers = new LinkedList<>(); + + public SubscribeeImpl(V init) { last = init; } + + public void publish(V b) { + synchronized (this) { + last = b; + for (Effect1 l : subscribers) { + l.f(b); + } + } + } + public V getLast() { + return last; + } + public void subscribe(Effect1 l) { + synchronized (this) { + subscribers.add(l); + } + } + public void unsubscribe(Effect1 l) { + synchronized (this) { + subscribers.remove(l); + } + } +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SuspendableBH.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SuspendableBH.java new file mode 100644 index 0000000..0596227 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/SuspendableBH.java @@ -0,0 +1,6 @@ +package com.acmetensortoys.android.teled.Utils; + +public abstract class SuspendableBH extends BehaviorHandle { + abstract public void suspend(); // request transition from RUNNING to SUSPENDED + // use start() to resume. +} \ No newline at end of file diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/ThreadBehaviorHandle.java b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/ThreadBehaviorHandle.java new file mode 100644 index 0000000..6b8e00f --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Utils/ThreadBehaviorHandle.java @@ -0,0 +1,50 @@ +package com.acmetensortoys.android.teled.Utils; + +import android.util.Log; + +import fj.Void; +import fj.function.Effect1; + +public class ThreadBehaviorHandle extends BehaviorHandle { + private final Thread thread; + private final Effect1 updateIn; + /* Actual liveness transitions are expected to be managed by the thread t */ + final private Subscribee ln; + + public ThreadBehaviorHandle(Thread t, Effect1 u, Subscribee ln) { + this.thread = t; + this.updateIn = u; + this.ln = ln; + } + + /* Given a runnable and an updater, make a behavior handle */ + public static ThreadBehaviorHandle create(final Runnable r, Effect1 u) { + final SubscribeeImpl lni + = new SubscribeeImpl<>(BehaviorHandle.BehaviorState.NEW); + Thread t = new Thread() { + public void run() { + lni.publish(BehaviorState.RUNNING); + r.run(); + lni.publish(BehaviorState.DEAD); + } + }; + return new ThreadBehaviorHandle(t, u, lni); + } + + /* Sometimes we don't even need an updater */ + public static ThreadBehaviorHandle create(final Runnable r) { + return create(r, new Effect1() { public void f(Void v) { v.absurd(); } }); + } + + @Override + public void join() throws InterruptedException { thread.join(); } + @Override + public void interrupt() { thread.interrupt(); } + @Override + public void start() { thread.start(); } + + @Override + public Effect1 getUpdateIn() { return updateIn; } + @Override + public Subscribee getStateSubscribee() { return ln; } +} \ No newline at end of file diff --git a/mobile/src/main/res/drawable/ic_info_black_24dp.xml b/mobile/src/main/res/drawable/ic_info_black_24dp.xml new file mode 100644 index 0000000..34b8202 --- /dev/null +++ b/mobile/src/main/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/src/main/res/drawable/ic_notifications_black_24dp.xml b/mobile/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..e3400cf --- /dev/null +++ b/mobile/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/src/main/res/drawable/ic_sync_black_24dp.xml b/mobile/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000..5a283aa --- /dev/null +++ b/mobile/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/mobile/src/main/res/layout/app_bar_main.xml b/mobile/src/main/res/layout/app_bar_main.xml index e00e4f7..bec9ee1 100644 --- a/mobile/src/main/res/layout/app_bar_main.xml +++ b/mobile/src/main/res/layout/app_bar_main.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" - tools:context="com.acmetensortoys.android.teled.MainActivity"> + tools:context=".UI.MainActivity"> + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/res/layout/newmain.xml b/mobile/src/main/res/layout/newmain.xml new file mode 100644 index 0000000..3fb22f9 --- /dev/null +++ b/mobile/src/main/res/layout/newmain.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/mobile/src/main/res/values-v21/styles.xml b/mobile/src/main/res/values-v21/styles.xml deleted file mode 100644 index 251fb9f..0000000 --- a/mobile/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ -> - - - diff --git a/mobile/src/main/res/values-w820dp/dimens.xml b/mobile/src/main/res/values-w820dp/dimens.xml deleted file mode 100644 index 63fc816..0000000 --- a/mobile/src/main/res/values-w820dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 64dp - diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 3bc5066..66ca8cd 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -5,4 +5,7 @@ Close navigation drawer Settings + Settings + + Acme Tensor Toys Demo diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index 545b9c6..497a8c7 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -17,4 +17,4 @@