package com.dappervision.wearscript.managers; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.dappervision.wearscript.BackgroundService; import com.dappervision.wearscript.Utils; import com.dappervision.wearscript.WearScript; import com.dappervision.wearscript.dataproviders.PebbleEventReceiver; import com.dappervision.wearscript.events.PebbleMessageEvent; import com.dappervision.wearscript.events.SendEvent; import com.dappervision.wearscript.events.SensorJSEvent; import com.getpebble.android.kit.PebbleKit; import com.getpebble.android.kit.util.PebbleDictionary; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class PebbleManager extends Manager{ private static final String TAG = "PebbleManager"; private final static UUID PEBBLE_APP_UUID = UUID.fromString("88c99af8-9512-4e23-b79e-ba437c788446"); private PebbleEventReceiver dataReceiver; private PebbleKit.PebbleAckReceiver ackReceiver; private PebbleKit.PebbleNackReceiver nackReceiver; private final PebbleMessageManager pebbleMessageManager; public static final String ONPEBBLE = "onPebble"; public static class Cmd { public static final int Cmd_setText = 0; public static final int Cmd_singleClick = 1; public static final int Cmd_longClick = 2; public static final int Cmd_accelTap = 3; public static final int Cmd_vibe = 4; public static final int Cmd_setScrollable = 5; public static final int Cmd_setStyle = 6; public static final int Cmd_setFullScreen = 7; public static final int Cmd_accelData = 8; public static final int Cmd_getAccelData = 9; public static final int Cmd_configAcceldata = 10; public static final int Cmd_configButtons = 11; } public static class Button { public static final int BACK = 0; public static final int UP = 1; public static final int SELECT = 2; public static final int DOWN = 3; } public static String parseButton(int button) { switch (button) { case Button.UP: return "UP"; case Button.SELECT: return "SELECT"; case Button.DOWN: return "DOWN"; case Button.BACK: return "BACK"; default: return null; } } public PebbleManager(Context context, BackgroundService bs) { super(bs); pebbleMessageManager = new PebbleMessageManager(context); reset(); } @Override public void reset() { super.reset(); teardown(); setup(); } @Override public void shutdown() { super.shutdown(); pebbleAccelSensorOff(); teardown(); } /* * Methods to communicate with pebble */ public void onPebbleSingleClick(String button) { makeCall("onPebbleSingleClick", String.format("'%s'", button)); makeCall("onPebbleSingleClick" + button, ""); String device = "TODO"; Utils.eventBusPost(new SendEvent(String.format("gesture:pebble:singleClick:%s:%s", button, device), "singleClick", button)); } public void onPebbleLongClick(String button) { makeCall("onPebbleLongClick", String.format("'%s'", button)); makeCall("onPebbleLongClick" + button, ""); String device = "TODO"; Utils.eventBusPost(new SendEvent(String.format("gesture:pebble:longClick:%s:%s", button, device), "longClick", button)); } public void onPebbleAccelTap(int axis, int direction) { makeCall("onPebbleAccelTap", String.format("'%d, %d'", axis, direction)); String device = "TODO"; Utils.eventBusPost(new SendEvent("gesture:pebble:accelTap:" + device, "accelTap", axis, direction)); } public void pebbleSetTitle(String title, boolean clear) { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_setText); data.addString(1, title); if(clear) data.addInt32(4, 1); pebbleMessageManager.offer(data); } public void pebbleSetBody(String body, boolean clear) { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_setText); data.addString(3, body); if(clear) data.addInt32(4, 1); pebbleMessageManager.offer(data); } public void pebbleSetSubTitle(String subTitle, boolean clear) { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_setText); data.addString(2, subTitle); if(clear) data.addInt32(4, 1); pebbleMessageManager.offer(data); } public void pebbleVibe(int type) { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_vibe); data.addInt32(1, type); pebbleMessageManager.offer(data); } public void pebbleAccelSensorOn(int rate) { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_configAcceldata); data.addInt32(1, rate); // Set rate 10 25 50 100 data.addInt32(2, 1); // Hard code 1 for data samples data.addInt32(3, 1); // subscribe pebbleMessageManager.offer(data); } public void pebbleAccelSensorOff() { PebbleDictionary data = new PebbleDictionary(); data.addInt8(0, (byte) Cmd.Cmd_configAcceldata); data.addInt32(3, 0); pebbleMessageManager.offer(data); } public void pebbleconfigGesture(boolean up, boolean down, boolean back, boolean select) { PebbleDictionary data = new PebbleDictionary(); // set up all buttons data.addInt32(1, up ? 1 : 0); data.addInt32(2, select ? 1 : 0); data.addInt32(3, down ? 1 : 0); data.addInt32(4, back ? 1 : 0); } public void onEvent(PebbleMessageEvent event) { String type = event.getType(); Log.i("manager on event", type); if(type.equals("setTitle")) pebbleSetTitle(event.getText(), event.getClear()); if(type.equals("setSubtitle")) pebbleSetSubTitle(event.getText(), event.getClear()); if(type.equals("setBody")) pebbleSetBody(event.getText(), event.getClear()); if(type.equals("vibe")) { pebbleVibe(event.getVibeType()); } } public void onEvent(SensorJSEvent event) { int type = event.getType(); if(event.getStatus()) { if (type == WearScript.SENSOR.PEBBLE_ACCELEROMETER.id()) pebbleAccelSensorOn((int) event.getSampleTime()); } else { pebbleAccelSensorOff(); } } private void teardown() { if(dataReceiver != null) { service.getApplicationContext().unregisterReceiver(dataReceiver); dataReceiver = null; } if (ackReceiver != null) { service.getApplicationContext().unregisterReceiver(ackReceiver); ackReceiver = null; } if (nackReceiver != null) { service.getApplicationContext().unregisterReceiver(nackReceiver); nackReceiver = null; } } private void setup() { // Start thread for sending messages new Thread(pebbleMessageManager).start(); // Set up broadcast receivers dataReceiver = new PebbleEventReceiver(PEBBLE_APP_UUID, this); PebbleKit.registerReceivedDataHandler(service.getApplicationContext(), dataReceiver); ackReceiver = new PebbleKit.PebbleAckReceiver(PEBBLE_APP_UUID) { @Override public void receiveAck(Context context, int transactionId) { pebbleMessageManager.notifyAckReceivedAsync(); Log.i("receiveAck", " " + transactionId); } }; PebbleKit.registerReceivedAckHandler(service.getApplicationContext(), ackReceiver); nackReceiver = new PebbleKit.PebbleNackReceiver(PEBBLE_APP_UUID) { @Override public void receiveNack(Context context, int transactionId) { pebbleMessageManager.notifyNackReceivedAsync(); Log.i("receiveNack", " " + transactionId); } }; PebbleKit.registerReceivedNackHandler(service.getApplicationContext(), nackReceiver); } /* Class to handle pebble messaging */ public class PebbleMessageManager implements Runnable { public Handler messageHandler; private final BlockingQueue<PebbleDictionary> messageQueue = new LinkedBlockingQueue<PebbleDictionary>(); private Boolean isMessagePending = Boolean.valueOf(false); private Context context; public PebbleMessageManager(Context context) { this.context = context; } @Override public void run() { Looper.prepare(); messageHandler = new Handler() { @Override public void handleMessage(Message msg) { Log.w(this.getClass().getSimpleName(), "Please post() your blocking runnables to Mr Manager, " + "don't use sendMessage()"); } }; Looper.loop(); } private void consumeAsync() { messageHandler.post(new Runnable() { @Override public void run() { synchronized (isMessagePending) { if (isMessagePending.booleanValue()) { return; } synchronized (messageQueue) { if (messageQueue.size() == 0) { return; } PebbleKit.sendDataToPebble(context, PEBBLE_APP_UUID, messageQueue.peek()); } isMessagePending = Boolean.valueOf(true); } } }); } public void notifyAckReceivedAsync() { messageHandler.post(new Runnable() { @Override public void run() { synchronized (isMessagePending) { isMessagePending = Boolean.valueOf(false); } if(!messageQueue.isEmpty()) messageQueue.remove(); } }); consumeAsync(); } public void notifyNackReceivedAsync() { messageHandler.post(new Runnable() { @Override public void run() { synchronized (isMessagePending) { isMessagePending = Boolean.valueOf(false); } } }); consumeAsync(); } public boolean offer(final PebbleDictionary data) { final boolean success = messageQueue.offer(data); if (success) { consumeAsync(); } return success; } } }