--- /dev/null
+*.iml
+.gradle
+/local.properties
+.DS_Store
+/build
+/captures
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
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"
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
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'
+}
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.acmetensortoys.android.teled">
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.SEND_SMS" />
- <uses-permission android:name="android.permission.RECEIVE_SMS" />
- <uses-permission android:name="android.permission.BLUETOOTH" />
- <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.BLUETOOTH" /> <!-- IOIO -->
+
+ <!-- <uses-permission android:name="android.permission.BODY_SENSORS" /> -->
+ <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
+ <uses-permission android:name="android.permission.RECEIVE_SMS" /> <!-- SMSRecv -->
+ <uses-permission android:name="android.permission.SEND_SMS" /> <!-- -->
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+ <!-- <uses-permission android:name="android.permission.WAKE_LOCK" /> -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
- android:name=".MainActivity"
- android:label="@string/app_name"
- android:theme="@style/AppTheme.NoActionBar">
+ android:name=".UI.MainActivity"
+ android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
- android:name=".IOIOService"
- android:enabled="true" />
-
- <service
- android:name=".RemoteControlService"
+ android:name=".Service.TeleDService"
android:enabled="true" />
+ <!--
<receiver
- android:name=".SMSRecv"
+ android:name=".Service.SMSRecv"
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_SMS">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
+ -->
+
+ <service
+ android:name=".Service.EphemeralTeleDService"
+ android:exported="false"></service>
</application>
-</manifest>
+</manifest>
\ No newline at end of file
--- /dev/null
+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<Void> makeBlinkOnce(final int pinId, final int dur) {
+ return new BehaviorFactory<Void>() {
+ public Behavior<Void> create(IOIO ioio_, Collection<Closeable> cc)
+ throws Exception {
+ final DigitalOutput pin = ioio_.openDigitalOutput(pinId);
+ cc.add(pin);
+ return new Behavior<Void>() {
+ public void run() throws Exception {
+ pin.write(false);
+ Thread.sleep(dur);
+ pin.write(true);
+ }
+ public void updateIn(Void v) { v.absurd(); }
+ };
+ }
+ };
+ }
+ static public BehaviorFactory<Void> makeBlinkLEDOnce(int dur) {
+ return makeBlinkOnce(IOIO.LED_PIN, dur);
+ }
+
+ static private BehaviorFactory<Void> makeBlinkMany(final int pinId, final int dur) {
+ return new BehaviorFactory<Void>() {
+ public Behavior<Void> create(IOIO ioio_, Collection<Closeable> cc)
+ throws Exception {
+ final DigitalOutput pin = ioio_.openDigitalOutput(pinId);
+ cc.add(pin);
+ return new Behavior<Void>() {
+ 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<Void> makeBlinkLEDMany(int dur) {
+ return makeBlinkMany(IOIO.LED_PIN, dur);
+ }
+
+ static public BehaviorFactory<Float> makePWMListener() {
+ return new BehaviorFactory<Float>() {
+ public Behavior<Float> create(IOIO ioio_, Collection<Closeable> 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<Float>() {
+ 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<Void> makeForceStreamer(final Effect1<Double> cb) {
+ return new BehaviorFactory<Void>() {
+ public Behavior<Void> create(IOIO ioio_, Collection<Closeable> cc) throws Exception {
+ final AnalogInput sensor = ioio_.openAnalogInput(31);
+ cc.add(sensor);
+ sensor.setBuffer(50);
+ return new Behavior<Void>() {
+ 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<Void> makeForceFeedback(final Effect1<Double> cb) {
+ return new BehaviorFactory<Void>() {
+ public Behavior<Void> create(IOIO ioio_, Collection<Closeable> 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<Void>() {
+ 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
--- /dev/null
+package com.acmetensortoys.android.teled.IOIO;
+
+import java.util.Collection;
+
+import ioio.lib.api.Closeable;
+import ioio.lib.api.IOIO;
+
+public interface BehaviorFactory<In> {
+ abstract class Behavior<In> {
+ @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<In> create(IOIO i, Collection<Closeable> cc) throws Exception;
+}
\ No newline at end of file
--- /dev/null
+// 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<Boolean> ioioStatus = new SubscribeeImpl<>(false);
+ public Subscribee<Boolean> 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 <In> BehaviorHandle<In> makeBehaviorThread(BehaviorFactory<In> bf) {
+ IOIO ioio_;
+ synchronized (TeleDIOIOManager.this) {
+ ioio_ = ioio;
+ }
+ if (ioio_ == null) {
+ return null;
+ }
+ final BehaviorFactory.Behavior<In> b;
+ final ArrayList<Closeable> cc = new ArrayList<>(5);
+ try {
+ b = bf.create(ioio_, cc);
+ } catch (Exception e) {
+ for (Closeable c : cc) { c.close(); }
+ return null;
+ }
+ final SubscribeeImpl<BehaviorHandle.BehaviorState> 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<In>() {
+ public void f(In x) {
+ b.updateIn(x);
+ }
+ }, blni);
+ }
+}
\ No newline at end of file
+++ /dev/null
-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<Closeable> cc) throws Exception;
- }
-
- static private Factory makeBlinkOnce(final int pinId, final int dur) {
- return new Factory() {
- public Behavior create(IOIO ioio_, Collection<Closeable> 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<Closeable> 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<Closeable> 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);
- }
- }
- }
- };
- }
- };
- }
-}
+++ /dev/null
-// 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<OnReady> 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<Closeable> 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
+++ /dev/null
-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.
- */
+++ /dev/null
-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<Thread> 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;
- }
-}
+++ /dev/null
-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...
- */
- }
-}
--- /dev/null
+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");
+ }
+}
--- /dev/null
+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
--- /dev/null
+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...
+ */
+ }
+}
--- /dev/null
+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<BehaviorHandle<?>> bhs = new BehaviorHandleSet();
+
+ // Some behaviors pause and unpause on SMS preference or GPS preference (or both!)
+ private final BehaviorHandleSet<SuspendableBH<?>> smsbhs = new BehaviorHandleSet<>();
+ private final BehaviorHandleSet<SuspendableBH<?>> 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<Boolean> 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<Boolean> getIOIOStatus() { return ios.getStatus(); }
+ public <In> BehaviorHandle<In> addIOIOBehavior(
+ BehaviorHandleSet.Metadata meta,
+ BehaviorFactory<In> bf) {
+ return addActiveBehavior(meta, ios.makeBehaviorThread(bf));
+ }
+
+ public <In> BehaviorHandle<In>
+ addActiveBehavior(BehaviorHandleSet.Metadata meta, BehaviorHandle<In> bh) {
+ bhs.addActiveBehavior(meta, bh);
+ return bh;
+ }
+
+ public BehaviorHandle<?> getActiveBehaviorById(int id) {
+ return bhs.getBehaviorById(id);
+ }
+ public Map<Integer,BehaviorHandleSet.Metadata>
+ 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<Boolean> 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<Map<Integer,BehaviorHandleSet.Metadata>>() {
+ public void f(Map<Integer,BehaviorHandleSet.Metadata> 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
--- /dev/null
+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());
+ }
+ }
+}
--- /dev/null
+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<Boolean> tdiOnReady = new Effect1<Boolean>() {
+ 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<Integer,String> m = tds.getActiveBehaviors();
+ for (Map.Entry<Integer,String> 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<Void> 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.
+ */
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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<Void> {
+
+ // 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<BehaviorState> 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<BehaviorState> getStateSubscribee() {
+ return ln;
+ }
+
+ @Override
+ public final Effect1<Void> getUpdateIn() {
+ return null;
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+package com.acmetensortoys.android.teled.Utils;
+
+import fj.function.Effect1;
+
+public abstract class BehaviorHandle<In> {
+ 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<BehaviorState> getStateSubscribee(); // to be notified on any state transition
+
+ abstract public Effect1<In> getUpdateIn();
+}
\ No newline at end of file
--- /dev/null
+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<BH extends BehaviorHandle<?>> {
+ 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<BH,Integer> activeBehaviorIds = HashBiMap.create();
+ private final HashMap<Integer,Metadata> activeBehaviorMeta = new HashMap<>();
+ private final SubscribeeImpl<Map<Integer,Metadata>> abmSubee
+ = new SubscribeeImpl<Map<Integer,Metadata>>(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<BehaviorHandle.BehaviorState>() {
+ 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<BH>
+ getBehaviors() {
+ return activeBehaviorIds.keySet().iterator();
+ }
+
+ // Clone the behavior meta map for consumption of the outside world.
+ @SuppressWarnings("unchecked")
+ public Map<Integer,Metadata>
+ getActiveBehaviorMeta() {
+ synchronized (this) {
+ return (Map<Integer,Metadata>)(activeBehaviorMeta.clone());
+ }
+ }
+
+ public Subscribee<Map<Integer,Metadata>>
+ getActiveBehaviorSubee() { return abmSubee; }
+
+ public static void showMetaMap(StringBuilder b, Map<Integer,Metadata> m) {
+ for(Map.Entry<Integer,Metadata> x : m.entrySet()) {
+ b.append(x.getKey());
+ b.append(" ");
+ b.append(x.getValue().name);
+ b.append("\n");
+ }
+ }
+}
--- /dev/null
+package com.acmetensortoys.android.teled.Utils;
+
+import fj.function.Effect1;
+
+public interface Subscribee<V> {
+ V getLast();
+ void subscribe(Effect1<V> cb);
+ void unsubscribe(Effect1<V> cb);
+}
\ No newline at end of file
--- /dev/null
+package com.acmetensortoys.android.teled.Utils;
+
+import java.util.LinkedList;
+import java.util.List;
+import fj.function.Effect1;
+
+public class SubscribeeImpl<V> implements Subscribee<V> {
+ private V last;
+ private final List<Effect1<V>> subscribers = new LinkedList<>();
+
+ public SubscribeeImpl(V init) { last = init; }
+
+ public void publish(V b) {
+ synchronized (this) {
+ last = b;
+ for (Effect1<V> l : subscribers) {
+ l.f(b);
+ }
+ }
+ }
+ public V getLast() {
+ return last;
+ }
+ public void subscribe(Effect1<V> l) {
+ synchronized (this) {
+ subscribers.add(l);
+ }
+ }
+ public void unsubscribe(Effect1<V> l) {
+ synchronized (this) {
+ subscribers.remove(l);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.acmetensortoys.android.teled.Utils;
+
+public abstract class SuspendableBH<In> extends BehaviorHandle<In> {
+ abstract public void suspend(); // request transition from RUNNING to SUSPENDED
+ // use start() to resume.
+}
\ No newline at end of file
--- /dev/null
+package com.acmetensortoys.android.teled.Utils;
+
+import android.util.Log;
+
+import fj.Void;
+import fj.function.Effect1;
+
+public class ThreadBehaviorHandle<In> extends BehaviorHandle<In> {
+ private final Thread thread;
+ private final Effect1<In> updateIn;
+ /* Actual liveness transitions are expected to be managed by the thread t */
+ final private Subscribee<BehaviorState> ln;
+
+ public ThreadBehaviorHandle(Thread t, Effect1<In> u, Subscribee<BehaviorState> ln) {
+ this.thread = t;
+ this.updateIn = u;
+ this.ln = ln;
+ }
+
+ /* Given a runnable and an updater, make a behavior handle */
+ public static <In> ThreadBehaviorHandle<In> create(final Runnable r, Effect1<In> u) {
+ final SubscribeeImpl<BehaviorHandle.BehaviorState> 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<In>(t, u, lni);
+ }
+
+ /* Sometimes we don't even need an updater */
+ public static ThreadBehaviorHandle<Void> create(final Runnable r) {
+ return create(r, new Effect1<Void>() { 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<In> getUpdateIn() { return updateIn; }
+ @Override
+ public Subscribee<BehaviorState> getStateSubscribee() { return ln; }
+}
\ No newline at end of file
--- /dev/null
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
+</vector>
--- /dev/null
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
+</vector>
--- /dev/null
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
+</vector>
\ No newline at end of file
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
- tools:context="com.acmetensortoys.android.teled.MainActivity">
+ tools:context=".UI.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
- tools:context="com.acmetensortoys.android.teled.MainActivity"
+ tools:context=".UI.MainActivity"
tools:showIn="@layout/app_bar_main">
<TextView
--- /dev/null
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".UI.ExampleFragment">
+
+ <android.support.design.widget.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="@style/AppTheme.AppBarOverlay">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ app:popupTheme="@style/AppTheme.PopupOverlay"
+ />
+
+ </android.support.design.widget.AppBarLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:showIn="@layout/app_bar_main"
+ android:orientation="vertical"
+ android:weightSum="1">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="27dp"
+ android:text="@string/frag_main_head" />
+
+ <Switch
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="SMS Reception Enabled"
+ android:id="@+id/main_frag_sms_ena"
+ android:checked="false" />
+
+ </LinearLayout>
+
+</android.support.design.widget.CoordinatorLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".UI.MainActivity">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/main_fragment_container">
+
+ </RelativeLayout>
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
+++ /dev/null
-<resources>>
-
- <style name="AppTheme.NoActionBar">
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- <item name="android:windowDrawsSystemBarBackgrounds">true</item>
- <item name="android:statusBarColor">@android:color/transparent</item>
- </style>
-</resources>
+++ /dev/null
-<resources>
- <!-- Example customization of dimensions originally defined in res/values/dimens.xml
- (such as screen margins) for screens with more than 820dp of available width. This
- would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
- <dimen name="activity_horizontal_margin">64dp</dimen>
-</resources>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="action_settings">Settings</string>
+ <string name="title_activity_settings">Settings</string>
+
+ <string name="frag_main_head">Acme Tensor Toys Demo</string>
</resources>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
-</resources>
+</resources>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <CheckBoxPreference
+ android:id="@+id/frag_pref_test"
+ android:key="test"
+ android:title="Test" />
+
+ <PreferenceCategory android:title="SMS">
+
+ <CheckBoxPreference
+ android:id="@+id/frag_pref_sms_ena"
+ android:key="sms_ena"
+ android:title="Enabled"
+ />
+
+ <PreferenceScreen
+ android:id="@+id/frag_pref_sms_pick"
+ android:key="sms_pick"
+ android:title="Pick contact">
+ </PreferenceScreen>
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="Position Reporting">
+
+ <CheckBoxPreference
+ android:id="@+id/frag_pref_gps_ena"
+ android:key="gps_ena"
+ android:title="Enabled"
+ />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="IOIO">
+
+ <CheckBoxPreference
+ android:id="@+id/frag_pref_ioio_ena"
+ android:key="ioio_ena"
+ android:title="Enabled"
+ />
+
+ <!--
+ <ListPreference
+ android:id="@+id/frag_pref_ioio_mac"
+ android:key="ioio_mac"
+ android:title="Bluetooth MAC Address"
+ />
+ -->
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
android {
compileSdkVersion 23
- buildToolsVersion "23.0.2"
+ buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.acmetensortoys.android.teled"
- minSdkVersion 21
+ minSdkVersion 23
targetSdkVersion 23
versionCode 1
versionName "1.0"
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:+'
- compile 'com.google.android.gms:play-services-wearable:+'
+ // compile 'com.google.android.gms:play-services-wearable:+'
}
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="com.acmetensortoys.android.teled.MainActivity"
+ tools:context="com.acmetensortoys.android.teled.UI.MainActivity"
tools:deviceIds="wear">
<TextView