package com.dappervision.wearscript;
import android.app.Activity;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.media.AudioRecord;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.dappervision.wearscript.dataproviders.BatteryDataProvider;
import com.dappervision.wearscript.dataproviders.DataPoint;
import com.dappervision.wearscript.events.ActivityEvent;
import com.dappervision.wearscript.events.CameraEvents;
import com.dappervision.wearscript.events.DataLogEvent;
import com.dappervision.wearscript.events.JsCall;
import com.dappervision.wearscript.events.LambdaEvent;
import com.dappervision.wearscript.events.SayEvent;
import com.dappervision.wearscript.events.ScreenEvent;
import com.dappervision.wearscript.events.ScriptEvent;
import com.dappervision.wearscript.events.SendEvent;
import com.dappervision.wearscript.events.ShutdownEvent;
import com.dappervision.wearscript.events.WifiScanResultsEvent;
import com.dappervision.wearscript.handlers.HandlerHandler;
import com.dappervision.wearscript.managers.CameraManager;
import com.dappervision.wearscript.managers.CardTreeManager;
import com.dappervision.wearscript.managers.ConnectionManager;
import com.dappervision.wearscript.managers.DataManager;
import com.dappervision.wearscript.managers.EyeManager;
import com.dappervision.wearscript.managers.GestureManager;
import com.dappervision.wearscript.managers.Manager;
import com.dappervision.wearscript.managers.ManagerManager;
import com.dappervision.wearscript.managers.PebbleManager;
import com.dappervision.wearscript.managers.PicarusManager;
import com.dappervision.wearscript.managers.WarpManager;
import com.dappervision.wearscript.managers.WifiManager;
import com.dappervision.wearscript.ui.ScriptActivity;
import com.getpebble.android.kit.PebbleKit;
import org.msgpack.MessagePack;
import org.msgpack.type.Value;
import org.msgpack.type.ValueFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.TreeMap;
public class BackgroundService extends Service implements AudioRecord.OnRecordPositionUpdateListener, OnInitListener {
protected static String TAG = "BackgroundService";
private final IBinder mBinder = new LocalBinder();
private final Object lock = new Object(); // All calls to webview client must acquire lock
protected TextToSpeech tts;
protected ScreenBroadcastReceiver broadcastReceiver;
protected String glassID;
private ScriptActivity activity;
private boolean dataRemote, dataLocal, dataWifi;
private double lastSensorSaveTime, sensorDelay;
private ScriptView webview;
private TreeMap<String, ArrayList<Value>> sensorBuffer;
private TreeMap<String, Integer> sensorTypes;
private MessagePack msgpack = new MessagePack();
private View activityView;
private ActivityEvent.Mode activityMode;
private String initScript;
static public String getDefaultUrl() {
byte[] wsUrlArray = Utils.LoadData("", "qr.txt");
if (wsUrlArray == null) {
Utils.eventBusPost(new SayEvent("Must setup wear script", false));
return "";
}
return (new String(wsUrlArray)).trim();
}
static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
public void updateActivityView(final ActivityEvent.Mode mode) {
Log.d(TAG, "updateActivityView: " + mode.toString());
if (activity == null) {
Log.d(TAG, "updateActivityView - activity is null, not setting");
return;
}
final ScriptActivity a = activity;
a.runOnUiThread(new Thread() {
public void run() {
activityMode = mode;
if (mode == ActivityEvent.Mode.WEBVIEW && webview != null) {
activityView = webview;
} else if (mode == ActivityEvent.Mode.WARP) {
activityView = ((WarpManager) getManager(WarpManager.class)).getView();
} else if (mode == ActivityEvent.Mode.CARD_TREE) {
activityView = ((CardTreeManager) getManager(CardTreeManager.class)).getView();
}
if (activityView != null) {
ViewGroup parentViewGroup = (ViewGroup) activityView.getParent();
if (parentViewGroup != null)
parentViewGroup.removeAllViews();
a.setContentView(activityView);
} else {
Log.i(TAG, "Not setting activity view because it is null: " + mode);
}
}
});
}
public void refreshActivityView() {
updateActivityView(activityMode);
}
public View getActivityView() {
return activityView;
}
public ActivityEvent.Mode getActivityMode() {
return activityMode;
}
public void loadUrl(String url) {
synchronized (lock) {
if (webview != null && url != null) {
webview.loadUrl(url);
}
}
}
public void handleSensor(DataPoint dp, String url) {
synchronized (lock) {
if (webview != null && url != null) {
//webview.loadUrl(url);
Utils.eventBusPost(new JsCall(url));
}
if (dataRemote || dataLocal) {
Integer type = dp.getType();
String name = dp.getName();
if (!sensorBuffer.containsKey(name)) {
sensorBuffer.put(name, new ArrayList<Value>());
sensorTypes.put(name, type);
}
sensorBuffer.get(name).add(dp.getValue());
if (System.nanoTime() - lastSensorSaveTime > sensorDelay) {
lastSensorSaveTime = System.nanoTime();
saveSensors();
}
}
}
}
public void saveSensors() {
final TreeMap<String, ArrayList<Value>> curSensorBuffer = sensorBuffer;
if (curSensorBuffer.isEmpty())
return;
ConnectionManager cm = (ConnectionManager) getManager(ConnectionManager.class);
ArrayList<Value> output = new ArrayList<Value>();
String channel = cm.subchannel(ConnectionManager.SENSORS_SUBCHAN);
output.add(ValueFactory.createRawValue(channel));
sensorBuffer = new TreeMap<String, ArrayList<Value>>();
if (!(dataRemote && cm.exists(channel)) && !dataLocal)
return;
ArrayList<Value> sensorTypes = new ArrayList<Value>();
for (String k : this.sensorTypes.navigableKeySet()) {
sensorTypes.add(ValueFactory.createRawValue(k));
sensorTypes.add(ValueFactory.createIntegerValue(this.sensorTypes.get(k)));
}
output.add(ValueFactory.createMapValue(sensorTypes.toArray(new Value[sensorTypes.size()])));
ArrayList<Value> sensors = new ArrayList<Value>();
for (String k : curSensorBuffer.navigableKeySet()) {
sensors.add(ValueFactory.createRawValue(k));
sensors.add(ValueFactory.createArrayValue(curSensorBuffer.get(k).toArray(new Value[curSensorBuffer.get(k).size()])));
}
output.add(ValueFactory.createMapValue(sensors.toArray(new Value[sensors.size()])));
final byte[] dataStr;
try {
dataStr = msgpack.write(output);
} catch (IOException e) {
Log.e(TAG, "Couldn't serialize msgpack");
e.printStackTrace();
return;
}
if (dataRemote && cm.exists(channel))
Utils.eventBusPost(new SendEvent(channel, dataStr));
if (dataLocal)
Utils.SaveData(dataStr, "data/", true, ".msgpack");
}
public void onEventAsync(CameraEvents.Frame frameEvent) {
Log.d(TAG, "CameraFrame Got: " + System.nanoTime());
try {
final CameraManager.CameraFrame frame = frameEvent.getCameraFrame();
// TODO(brandyn): Move this timing logic into the camera manager
Log.d(TAG, "handeImage Thread: " + Thread.currentThread().getName());
byte[] frameJPEG = null;
if (dataLocal) {
frameJPEG = frame.getJPEG();
// TODO(brandyn): We can improve timestamp precision by capturing it pre-encoding
Utils.SaveData(frameJPEG, "data/", true, ".jpg");
}
ConnectionManager cm = (ConnectionManager) getManager(ConnectionManager.class);
String channel = cm.subchannel(ConnectionManager.IMAGE_SUBCHAN);
if (dataRemote && cm.exists(channel)) {
if (frameJPEG == null)
frameJPEG = frame.getJPEG();
Utils.eventBusPost(new SendEvent(channel, System.currentTimeMillis() / 1000., ValueFactory.createRawValue(frameJPEG)));
}
// NOTE(brandyn): Done from here because the frame must have "done" called on it
((WarpManager) getManager(WarpManager.class)).processFrame(frameEvent);
((PicarusManager) getManager(PicarusManager.class)).processFrame(frameEvent);
} finally {
frameEvent.done();
}
}
public void shutdown() {
synchronized (lock) {
reset();
if (broadcastReceiver != null) {
unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
if (tts != null) {
tts.stop();
tts.shutdown();
}
Utils.getEventBus().unregister(this);
ManagerManager.get().shutdownAll();
HandlerHandler.get().shutdownAll();
if (activity == null)
return;
final ScriptActivity a = activity;
a.runOnUiThread(new Thread() {
public void run() {
Log.d(TAG, "Lifecycle: Stop self activity");
a.bs.stopSelf();
a.bs.activity = null;
a.bs = null;
a.finish();
}
});
}
}
public void reset() {
synchronized (lock) {
Log.d(TAG, "reset");
// NOTE(brandyn): Put in a better spot
if (webview != null) {
webview.stopLoading();
// Stops all javascript
webview.loadUrl("about:blank");
webview.onDestroy();
webview = null;
}
sensorBuffer = new TreeMap<String, ArrayList<Value>>();
sensorTypes = new TreeMap<String, Integer>();
dataWifi = dataRemote = dataLocal = false;
lastSensorSaveTime = sensorDelay = 0.;
ManagerManager.get().resetAll();
HandlerHandler.get().resetAll();
if(activity != null) {
ScriptActivity a = activity;
// TODO(brandyn): Verify that if we create a new activity that the gestures still work
if (HardwareDetector.isGlass && ManagerManager.get().get(GestureManager.class) == null) {
ManagerManager.get().add(new GestureManager(a, this));
}
}
if (PebbleKit.isWatchConnected(getApplicationContext()) && ManagerManager.get().get(PebbleManager.class) == null) {
if(activity != null) {
ScriptActivity a = activity;
ManagerManager.get().add(new PebbleManager(a, this));
}
}
updateActivityView(ActivityEvent.Mode.WEBVIEW);
}
}
public void startDefaultScript() {
byte[] data = "<body style='width:640px; height:480px; overflow:hidden; margin:0' bgcolor='black'><center><h1 style='font-size:70px;color:#FAFAFA;font-family:monospace'>WearScript</h1><h1 style='font-size:40px;color:#FAFAFA;font-family:monospace'>When connected use playground to control<br><br>Docs @ wearscript.com</h1></center><script>function s() {WSRAW.say('Connected')};window.onload=function () {WSRAW.serverConnect('{{WSUrl}}', 's')}</script></body>".getBytes();
String path = Utils.SaveData(data, "scripting/", false, "glass.html");
Utils.eventBusPost(new ScriptEvent(path));
}
public void wake() {
final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
final PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "BackgroundService");
wakeLock.acquire();
wakeLock.release();
}
public void onEventMainThread(ScriptEvent e) {
reset();
synchronized (lock) {
webview = createScriptView();
Log.d(TAG, "webview.isHardwareAccelerated: " + webview.isHardwareAccelerated());
updateActivityView(ActivityEvent.Mode.WEBVIEW);
webview.getSettings().setJavaScriptEnabled(true);
webview.addJavascriptInterface(new WearScript(this), "WSRAW");
webview.setInitialScale(100);
Log.i(TAG, "WebView: " + e.getScriptPath());
if (initScript != null && !initScript.isEmpty())
webview.loadUrl(initScript);
webview.loadUrl("file://" + e.getScriptPath());
Log.i(TAG, "WebView Ran");
}
}
public void onEventMainThread(LambdaEvent e) {
synchronized (lock) {
webview.loadUrl("javascript:" + e.getCommand());
}
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Service onBind");
return mBinder;
}
@Override
public void onCreate() {
Log.i(TAG, "Lifecycle: Service onCreate");
Utils.getEventBus().register(this);
//getResources().openRawResource(R.raw.init)
try {
initScript = "javascript:" + convertStreamToString(getAssets().open("init.js.min"));
} catch (IOException e) {
e.printStackTrace();
// TODO(brandyn): Handle
}
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
broadcastReceiver = new ScreenBroadcastReceiver(this);
registerReceiver(broadcastReceiver, intentFilter);
//Plugin new Managers here
ManagerManager.get().newManagers(this);
tts = new TextToSpeech(this, this);
glassID = ((WifiManager) getManager(WifiManager.class)).getMacAddress();
reset();
}
public void setMainActivity(ScriptActivity a) {
Log.i(TAG, "Lifecycle: BackgroundService: setMainActivity");
if (this.activity != null) {
activity.finish();
}
this.activity = a;
if (ManagerManager.hasManager(CardTreeManager.class))
((CardTreeManager) getManager(CardTreeManager.class)).setMainActivity(a);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service onStartCommand");
return START_STICKY;
}
@Override
public void onDestroy() {
Log.i(TAG, "Lifecycle: Service onDestroy");
shutdown();
super.onDestroy();
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (hasWebView())
webview.onConfigurationChanged(newConfig);
}
public void onEventMainThread(JsCall e) {
loadUrl(e.getCall());
}
public void onEventMainThread(SayEvent e) {
say(e.getMsg(), e.getInterrupt());
}
public void onEventMainThread(ActivityEvent e) {
if (e.getMode() == ActivityEvent.Mode.CREATE) {
CameraManager cm = ((CameraManager) getManager(CameraManager.class));
if (cm != null && cm.getActivityVisible())
return;
Intent i = new Intent(this, ScriptActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
} else if (e.getMode() == ActivityEvent.Mode.DESTROY) {
activity.finish();
} else if (e.getMode() == ActivityEvent.Mode.REFRESH) {
refreshActivityView();
} else {
updateActivityView(e.getMode());
}
}
public void onEvent(DataLogEvent e) {
dataRemote = e.isServer();
dataLocal = e.isLocal();
sensorDelay = e.getSensorDelay() * 1000000000L;
}
public void onEventMainThread(ScreenEvent e) {
wake();
}
public void onEventMainThread(ShutdownEvent e) {
shutdown();
}
@Override
public void onMarkerReached(AudioRecord arg0) {
Log.i(TAG, "Audio mark");
}
@Override
public void onPeriodicNotification(AudioRecord arg0) {
Log.i(TAG, "Audio period");
}
public void say(String text, boolean interrupt) {
if (tts == null)
return;
if (!tts.isSpeaking() || interrupt)
tts.speak(text, TextToSpeech.QUEUE_ADD,
null);
}
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
Utils.setupTTS(this, tts);
} else {
Log.w(TAG, "TTS initialization failed: " + status);
}
}
public Manager getManager(Class<? extends Manager> cls) {
return ManagerManager.get().get(cls);
}
public ScriptView createScriptView() {
ScriptView mCallback = new ScriptView(this);
mCallback.setBackgroundColor(0);
return mCallback;
}
public boolean onPrepareOptionsMenu(Menu menu, Activity activity) {
CardTreeManager cm = ((CardTreeManager) getManager(CardTreeManager.class));
if (cm != null)
return cm.onPrepareOptionsMenu(menu, activity);
return false;
}
public boolean onBackPressed() {
CardTreeManager cm = ((CardTreeManager) getManager(CardTreeManager.class));
if (cm == null || activityMode != ActivityEvent.Mode.CARD_TREE)
return true;
return cm.onBackPressed();
}
public boolean hasWebView() {
return webview != null;
}
public boolean onOptionsItemSelected(MenuItem item) {
CardTreeManager cm = ((CardTreeManager) getManager(CardTreeManager.class));
if (cm != null) {
return cm.onOptionsItemSelected(item);
}
return false;
}
class ScreenBroadcastReceiver extends BroadcastReceiver {
BackgroundService bs;
public ScreenBroadcastReceiver(BackgroundService bs) {
this.bs = bs;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.d(TAG, "Screen off");
CameraManager cm = ((CameraManager) bs.getManager(CameraManager.class));
if (cm != null) {
cm.screenOff();
}
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
Log.d(TAG, "Screen on");
CameraManager cm = ((CameraManager) bs.getManager(CameraManager.class));
if (cm != null) {
cm.screenOn();
}
} else if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
BatteryDataProvider dp = (BatteryDataProvider) ((DataManager) bs.getManager(DataManager.class)).getProvider(WearScript.SENSOR.BATTERY.id());
if (dp != null)
dp.post(intent);
} else if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
Log.d(TAG, "Wifi scan results");
Utils.eventBusPost(new WifiScanResultsEvent());
}
}
}
public class LocalBinder extends Binder {
public BackgroundService getService() {
return BackgroundService.this;
}
}
}