Add osmo service

This commit is contained in:
vshcherb 2014-05-14 18:17:50 +03:00
parent ad6924f17a
commit add5867aca
4 changed files with 384 additions and 143 deletions

View file

@ -1,7 +1,5 @@
package net.osmand.plus.osmo;
import java.io.IOException;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.plus.ContextMenuAdapter;
@ -9,18 +7,17 @@ import net.osmand.plus.ContextMenuAdapter.OnContextMenuClick;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.SettingsActivity;
import net.osmand.plus.views.MonitoringInfoControl;
import net.osmand.plus.views.MonitoringInfoControl.MonitoringInfoControlServices;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.activities.MapActivity;
import org.apache.commons.logging.Log;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
@ -30,11 +27,13 @@ public class OsMoPlugin extends OsmandPlugin implements MonitoringInfoControlSer
private OsmandApplication app;
public static final String ID = "osmand.osmo";
private static final Log log = PlatformUtil.getLog(OsMoPlugin.class);
//private static final Log log = PlatformUtil.getLog(OsMoPlugin.class);
private OsMoService service;
private OsMoTracker tracker;
public OsMoPlugin(final OsmandApplication app) {
service = new OsMoService();
tracker = new OsMoTracker(service);
this.app = app;
}
@ -45,13 +44,8 @@ public class OsMoPlugin extends OsmandPlugin implements MonitoringInfoControlSer
@Override
public void updateLocation(Location location) {
if (service.isActive()) {
try {
service.sendCoordinate(location.getLatitude(), location.getLongitude(), location.getAccuracy(),
(float) location.getAltitude(), location.getSpeed(), location.getBearing());
} catch (IOException e) {
log.error(e.getMessage(), e);
}
if (service.isConnected()) {
tracker.sendCoordinate(location);
}
}
@ -79,73 +73,22 @@ public class OsMoPlugin extends OsmandPlugin implements MonitoringInfoControlSer
}
}
private void connect(final boolean enable) {
new AsyncTask<Void, Void, String>() {
private Exception e;
@Override
protected String doInBackground(Void... params) {
try {
String response;
if (enable) {
response = service.activate(getUUID(app));
} else {
response = service.deactivate();
}
return response;
} catch (Exception e) {
this.e = e;
return null;
}
}
protected void onPostExecute(String result) {
if(e != null) {
app.showToastMessage(app.getString(R.string.osmo_io_error) + ": " + e.getMessage());
log.error(e.getMessage(), e);
} else {
app.showToastMessage(result + "");
}
};
}.execute();
}
private void sendCoordinate(final double lat, final double lon) {
new AsyncTask<Void, Void, String>() {
private Exception e;
@Override
protected String doInBackground(Void... params) {
try {
return service.sendCoordinate(lat, lon, 0, 0, 0,
0);
} catch (Exception e) {
this.e = e;
return null;
}
}
protected void onPostExecute(String result) {
if(e != null) {
app.showToastMessage(app.getString(R.string.osmo_io_error) + ": " + e.getMessage());
log.error(e.getMessage(), e);
} else {
app.showToastMessage(result + "");
}
};
}.execute();
}
@Override
public void addMonitorActions(ContextMenuAdapter qa, MonitoringInfoControl li, final OsmandMapTileView view) {
final boolean off = !service.isActive();
final boolean off = !service.isConnected();
qa.item(off ? R.string.osmo_mode_off : R.string.osmo_mode_on)
.icon(off ? R.drawable.monitoring_rec_inactive : R.drawable.monitoring_rec_big)
.listen(new OnContextMenuClick() {
@Override
public void onContextMenuClick(int itemId, int pos, boolean isChecked, DialogInterface dialog) {
connect(off);
if(off) {
service.connect(true);
} else {
service.disconnect();
}
}
@ -158,7 +101,7 @@ public class OsMoPlugin extends OsmandPlugin implements MonitoringInfoControlSer
public void onContextMenuClick(int itemId, int pos, boolean isChecked, DialogInterface dialog) {
final double lat = view.getLatitude();
final double lon = view.getLongitude();
sendCoordinate(lat, lon);
tracker.sendCoordinate(lat, lon);
}
}).reg();
}

View file

@ -1,92 +1,68 @@
package net.osmand.plus.osmo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;
import net.osmand.PlatformUtil;
import org.apache.commons.logging.Log;
import java.util.List;
public class OsMoService {
private static String TRACKER_SERVER = "srv.osmo.mobi";
private static int TRACKER_PORT = 4242;
public static int NUMBER_OF_TRIES_TO_RECONNECT = 20;
private Log log = PlatformUtil.getLog(OsMoService.class);
private OutputStreamWriter out;
private BufferedReader in;
private Socket socket;
private Queue<String> buffer = new LinkedList<String>();
private int numberOfFailures = 0;
private String loginHash;
public boolean isActive() {
return socket != null;
}
public String activate(String loginHash) throws IOException {
this.loginHash = loginHash;
socket = new Socket(TRACKER_SERVER, TRACKER_PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
//public static int NUMBER_OF_TRIES_TO_RECONNECT = 20;
private OsMoThread thread;
private List<OsMoSender> listSenders = new java.util.concurrent.CopyOnWriteArrayList<OsMoSender>();
private List<OsMoReactor> listReactors = new java.util.concurrent.CopyOnWriteArrayList<OsMoReactor>();
public interface OsMoSender {
String t = sendCommand("auth|"+loginHash);
t += sendCommand("session_open");
return t;
public String nextSendCommand();
}
private String sendCommand(String s) throws IOException {
s = prepareCommand(s);
log.info("OSMo command : " + s);
out.write(s);
return in.readLine();
}
private String prepareCommand(String s) {
if(s.endsWith("\n")) {
s += "\n";
}
return s;
public interface OsMoReactor {
public boolean acceptCommand(String command);
}
public String sendCoordinate(double lat, double lon, float hdop, float alt, float speed, float bearing) throws IOException {
return sendCommandWithBuffer("p|"+lat+":"+lon+":"+hdop+":"+alt+":"+speed+":"+bearing);
public OsMoService() {
}
private synchronized String sendCommandWithBuffer(String command) throws IOException {
buffer.add(command);
String lastResponse = null;
while (!buffer.isEmpty()) {
reconnectIfNeeded();
try {
lastResponse = sendCommand(command);
} catch (IOException e) {
numberOfFailures++;
e.printStackTrace();
public boolean isConnected() {
return thread != null && thread.isConnected();
}
public boolean connect(boolean forceReconnect) {
if(thread != null) {
if(!forceReconnect ) {
return isConnected();
}
thread.stopConnection();
}
return lastResponse;
thread = new OsMoThread(listSenders, listReactors);
return true;
}
private void reconnectIfNeeded() throws IOException {
if(numberOfFailures >= NUMBER_OF_TRIES_TO_RECONNECT) {
deactivate();
activate(this.loginHash);
numberOfFailures = 0;
public void disconnect() {
if(thread != null) {
thread.stopConnection();
}
}
public void registerSender(OsMoSender sender) {
if(!listSenders.contains(sender)) {
listSenders.add(sender);
}
}
public void registerReactor(OsMoReactor reactor) {
if(!listReactors.contains(reactor)) {
listReactors.add(reactor);
}
}
public String deactivate() throws IOException {
String t = sendCommand("session_close");
in.close();
out.close();
socket.close();
socket = null;
return t;
public void removeSender(OsMoSender s) {
listSenders.remove(s);
}
public void removeReactor(OsMoReactor s) {
listReactors.remove(s);
}
}

View file

@ -0,0 +1,273 @@
package net.osmand.plus.osmo;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import net.osmand.PlatformUtil;
import net.osmand.plus.osmo.OsMoService.OsMoReactor;
import net.osmand.plus.osmo.OsMoService.OsMoSender;
import org.apache.commons.logging.Log;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
public class OsMoThread {
private static String TRACKER_SERVER = "srv.osmo.mobi";
private static int TRACKER_PORT = 4242;
protected final static Log log = PlatformUtil.getLog(OsMoThread.class);
private static final long HEARTBEAT_DELAY = 100;
private static final long HEARTBEAT_FAILED_DELAY = 10000;
private static final long LIMIT_OF_FAILURES_RECONNECT = 10;
private static final long CONNECTION_DELAY = 25000;
private static final long SELECT_TIMEOUT = 500;
private static int HEARTBEAT_MSG = 3;
private Handler serviceThread;
private int failures = 0;
private int activeConnectionId = 0;
// -1 means connected, 0 needs to reconnect, > 0 when connection initiated
private long connectionStarted = 0;
private boolean stopThread;
private Selector selector;
private List<OsMoSender> listSenders;
private List<OsMoReactor> listReactors;
private SocketChannel activeChannel;
private ByteBuffer pendingSendCommand;
private String readCommand = "";
private ByteBuffer pendingReadCommand = ByteBuffer.allocate(2048);
private LinkedList<String> queueOfMessages = new LinkedList<String>();
public OsMoThread(List<OsMoSender> listSenders, List<OsMoReactor> listReactors) {
this.listSenders = listSenders;
this.listReactors = listReactors;
// start thread to receive events from OSMO
HandlerThread h = new HandlerThread("OSMo Service");
h.start();
serviceThread = new Handler(h.getLooper());
serviceThread.post(new Runnable() {
@Override
public void run() {
try {
initConnection();
} catch (IOException e) {
e.printStackTrace();
}
}
});
scheduleHeartbeat(HEARTBEAT_DELAY);
}
public void stopConnection() {
stopThread = true;
}
protected void initConnection() throws IOException {
try {
selector = Selector.open();
connectionStarted = System.currentTimeMillis();
activeChannel = SocketChannel.open();
activeChannel.configureBlocking(false);
SelectionKey key = activeChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
key.attach(new Integer(++activeConnectionId));
activeChannel.connect(new InetSocketAddress(TRACKER_SERVER, TRACKER_PORT));
} catch (IOException e) {
throw e;
}
}
public void scheduleHeartbeat(long delay) {
Message msg = serviceThread.obtainMessage();
msg.what = HEARTBEAT_MSG;
serviceThread.postDelayed(new Runnable() {
@Override
public void run() {
checkAsyncSocket();
}
}, delay);
}
public boolean isConnected() {
return connectionStarted == -1;
}
protected void checkAsyncSocket() {
long delay = HEARTBEAT_DELAY;
try {
if (selector == null) {
stopThread = true;
} else {
if (activeChannel != null && connectionStarted != -1 && !activeChannel.isConnectionPending()) {
// connection ready
connectionStarted = -1;
}
if ((connectionStarted != -1 && System.currentTimeMillis() - connectionStarted > CONNECTION_DELAY)
|| activeChannel == null) {
initConnection();
} else {
checkSelectedKeys();
}
}
} catch (Exception e) {
log.info("Exception selecting socket", e);
e.printStackTrace();
delay = HEARTBEAT_FAILED_DELAY;
if (activeChannel != null && !activeChannel.isConnected()) {
activeChannel = null;
}
if (failures++ > LIMIT_OF_FAILURES_RECONNECT) {
stopChannel();
}
}
if (stopThread) {
stopChannel();
serviceThread.getLooper().quit();
} else {
scheduleHeartbeat(delay);
}
}
private void stopChannel() {
if (activeChannel != null) {
try {
activeChannel.close();
} catch (IOException e) {
}
}
activeChannel = null;
}
private void checkSelectedKeys() throws IOException {
/* int s = */selector.select(SELECT_TIMEOUT);
Set<SelectionKey> keys = selector.selectedKeys();
if (keys == null) {
return;
}
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
final boolean isActive = new Integer(activeConnectionId).equals(key.attachment());
// final boolean isActive = activeChannel == key.channel();
if (isActive) {
if (key.isWritable()) {
writeCommands();
}
if (key.isReadable()) {
readCommands();
}
} else {
try {
key.channel().close();
} catch (Exception e) {
log.info("Exception closing channel", e);
e.printStackTrace();
}
}
iterator.remove();
}
}
private void readCommands() throws IOException {
boolean hasSomethingToRead = true;
while (hasSomethingToRead) {
pendingReadCommand.clear();
int read = activeChannel.read(pendingReadCommand);
if (pendingReadCommand.hasRemaining()) {
hasSomethingToRead = true;
} else {
hasSomethingToRead = false;
}
if (read > 0) {
byte[] ar = pendingReadCommand.array();
String res = new String(ar, 0, read);
readCommand += res;
int i;
while ((i = readCommand.indexOf('\n')) != -1) {
String cmd = readCommand.substring(0, i);
readCommand = readCommand.substring(i + 1);
queueOfMessages.add(cmd.replace("\\n", "\n"));
}
}
}
if (queueOfMessages.size() > 0) {
while(!queueOfMessages.isEmpty()){
String cmd = queueOfMessages.poll();
boolean processed = false;
for (OsMoReactor o : listReactors) {
if (o.acceptCommand(cmd)) {
processed = true;
break;
}
}
if (!processed) {
log.warn("Command not processed '" + cmd + "'");
}
}
}
}
private void writeCommands() throws UnsupportedEncodingException, IOException {
if (pendingSendCommand == null) {
getNewPendingSendCommand();
}
while (pendingSendCommand != null) {
activeChannel.write(pendingSendCommand);
if (!pendingSendCommand.hasRemaining()) {
pendingSendCommand = null;
getNewPendingSendCommand();
}
}
}
private void getNewPendingSendCommand() throws UnsupportedEncodingException {
for (OsMoSender s : listSenders) {
String l = s.nextSendCommand();
if (l != null) {
StringBuilder res = prepareCommand(l);
pendingSendCommand = ByteBuffer.wrap(res.toString().getBytes("UTF-8"));
break;
}
}
}
private StringBuilder prepareCommand(String l) {
boolean addNL = true;
StringBuilder res = new StringBuilder();
int i = 0;
while (true) {
int ni = l.indexOf('\n');
if (ni == l.length() - 1) {
res.append(l.substring(i));
addNL = false;
break;
} else if (ni == -1) {
res.append(l.substring(i));
break;
} else {
res.append(l.substring(i, ni));
res.append("\\").append("n");
}
i = ni + 1;
}
if (addNL) {
l += "\n";
}
return res;
}
}

View file

@ -0,0 +1,49 @@
package net.osmand.plus.osmo;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import net.osmand.Location;
import net.osmand.plus.osmo.OsMoService.OsMoSender;
public class OsMoTracker implements OsMoSender {
private LinkedList<Location> bufferOfLocations = new LinkedList<Location>();
public OsMoTracker(OsMoService service) {
service.registerSender(this);
}
@Override
public String nextSendCommand() {
if(!bufferOfLocations.isEmpty()){
Location loc = bufferOfLocations.poll();
StringBuilder cmd = new StringBuilder("T|");
cmd.append("L").append((float)loc.getLatitude()).append(":").append((float)loc.getLongitude());
if(loc.hasAccuracy()) {
cmd.append("H").append((float)loc.getAccuracy());
}
if(loc.hasAltitude()) {
cmd.append("A").append((float)loc.getAltitude());
}
if(loc.hasSpeed()) {
cmd.append("S").append((float)loc.getSpeed());
}
return cmd.toString();
}
return null;
}
public void sendCoordinate(Location location) {
bufferOfLocations.add(location);
}
public void sendCoordinate(double lat, double lon) {
Location l = new Location("test");
l.setLatitude(lat);
l.setLongitude(lon);
bufferOfLocations.add(l);
}
}