/*
* XCTrack - XContest Live Tracking client for J2ME devices
* Copyright (C) 2009 Petr Chromec <petr@xcontest.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.xcontest.xctrack.info;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import org.xcontest.live.GpsPoint;
import org.xcontest.live.LiveClient;
import org.xcontest.live.LiveClientListener;
import org.xcontest.live.LiveConnection;
import org.xcontest.live.LiveTCPConnection;
import org.xcontest.live.LiveUDPConnection;
import org.xcontest.live.Logger;
import org.xcontest.live.TrackType;
import org.xcontest.xctrack.App;
import org.xcontest.xctrack.Util;
import org.xcontest.xctrack.config.Config;
import org.xcontest.xctrack.gps.GpsDeviceInfo;
import org.xcontest.xctrack.gps.GpsDriver;
import org.xcontest.xctrack.gps.GpsListener;
import org.xcontest.xctrack.gps.GpsMessage;
import org.xcontest.xctrack.settings.Profile;
import org.xcontest.xctrack.util.Log;
import org.xcontest.xctrack.widget.WidgetPage;
class LiveLogger implements Logger {
LiveLogger() {
}
public void info(String message) {
Log.info(message);
}
public void error(String message) {
Log.error(message);
}
public void error(String message, Throwable ex) {
Log.error(message,ex);
}
}
public class InfoCenter extends Thread
implements LiveClientListener, GpsListener {
private final int BACKLIGHT_REFRESH_INTERVAL=5000; // ms
private final int STATE_STOPPED=1;
private final int STATE_LOGIN=2;
private final int STATE_TRACKING=3;
private final int ACTION_STOP_TRACKING=1;
private final int ACTION_CONTINUE_TRACKING=2;
private final int ACTION_START_TRACKING=3;
private final int ACTION_LOGIN_OK = 4;
private final int ACTION_LOGIN_FAILED = 5;
private final int ACTION_UPDATE_LIVE_STATUS = 6;
private final int ACTION_BACKLIGHT_ON = 7;
private final int ACTION_BACKLIGHT_OFF = 8;
private final int ACTION_EXIT = 9;
private LocationInfo _locationInfo;
private LiveInfo _liveInfo;
private LogInfo _logInfo;
private LiveClient _live;
private WidgetPage _widgetPage;
// modified in InfoCenter thread only
private int _state;
private LiveConnection _conn;
private GpsDriver _gpsDriver;
private boolean _hasBacklight;
private boolean _keepsBacklight;
private long _nextBacklightRefresh;
private long _nextWidgetPageRepaint;
// modified from more threads
private Profile _activeProfile;
private double _nextLivePointTime;
// queues & info forwarding to thread
private Vector _queueActions;
private Vector _queueLivePoints;
//////////////////////////
private static InfoCenter _instance;
public static InfoCenter getInstance() {
return _instance;
}
public LocationInfo getLocationInfo() {
return _locationInfo;
}
public LiveInfo getLiveInfo() {
return _liveInfo;
}
public LogInfo getLogInfo() {
return _logInfo;
}
/*******************************************************
*
* Commands
*
*/
public synchronized boolean keepsBacklight() {
return _keepsBacklight;
}
public synchronized void keepBacklight(boolean val) {
if (val)
_queueActions.addElement(new Integer(ACTION_BACKLIGHT_ON));
else
_queueActions.addElement(new Integer(ACTION_BACKLIGHT_OFF));
notify();
}
public synchronized void exit() {
if (_state != STATE_STOPPED) { // if we are tracking stop it
_queueActions.addElement(new Integer(ACTION_STOP_TRACKING));
}
_queueActions.addElement(new Integer(ACTION_EXIT));
notify();
}
public synchronized void stopTracking() {
if (_state != STATE_STOPPED) {
_queueActions.addElement(new Integer(ACTION_STOP_TRACKING));
notify();
}
}
public synchronized void continueTracking() {
if (_state == STATE_STOPPED) {
_activeProfile = Config.getLastTrackProfile();
Util.expect(_activeProfile != null, "continueTracking(): Last track profile not found!");
_queueActions.addElement(new Integer(ACTION_CONTINUE_TRACKING));
notify();
}
}
public synchronized void startTracking(Profile profile) {
if (_state == STATE_STOPPED) {
_activeProfile = profile;
_queueActions.addElement(new Integer(ACTION_START_TRACKING));
notify();
}
}
/*******************************************************
*
* Event handling
*
*/
// GpsListener
public void deviceConnected() {
_locationInfo.setGpsConnected(true);
}
// GpsListener
public void deviceDisconnected() {
_locationInfo.setGpsConnected(false);
}
// GpsListener
public void signalLost() {
_locationInfo.setHasGpsSignal(false);
}
// GpsListener
public void signalReached() {
_locationInfo.setHasGpsSignal(true);
}
// GpsListener
public synchronized void gpsMessage(GpsMessage msg) {
// pass message to locationInfo
_locationInfo.update(msg);
// process the message for passing the position to LiveClient
if (msg.hasPosition) {
long time;
if (msg.hasTime)
time = msg.time;
else
time = (long)(_locationInfo.computeTime()*1000);
if (_nextLivePointTime*1000 <= time) {
GpsPoint p = new GpsPoint();
p.setTime(time);
p.lat = msg.lat;
p.lon = msg.lon;
p.alt = msg.hasAltitude ? msg.altitude : 0;
_queueLivePoints.addElement(p);
_nextLivePointTime += _activeProfile.getTracklogInterval();
if (_nextLivePointTime < p.time)
_nextLivePointTime = p.time;
notify();
}
}
}
// LiveClientListener
public synchronized void connectionOpened(String remote) {
if (_state != STATE_STOPPED) {
_liveInfo.setConnected(true);
}
}
// LiveClientListener
public synchronized void openConnectionFailed(String remote) {
if (_state != STATE_STOPPED) {
_liveInfo.setConnected(false);
}
}
// LiveClientListener - start tracking successful
public synchronized void loginOK(String key, String domain, String username) {
if (_state == STATE_LOGIN) {
Config.setLastTrackKey(key);
Config.setLastTrackProfile(_activeProfile);
Config.writeAll();
_queueActions.addElement(new Integer(ACTION_LOGIN_OK));
notify();
}
}
// LiveClientListener
public synchronized void loginFailed(String domain, String username) {
if (_state == STATE_LOGIN) {
_queueActions.addElement(new Integer(ACTION_LOGIN_FAILED));
notify();
}
}
// LiveClientListener
public synchronized void statusUpdate() {
if (_state != STATE_STOPPED) {
_queueActions.addElement(new Integer(ACTION_UPDATE_LIVE_STATUS));
notify();
}
}
public InfoCenter() {
Util.expect(_instance == null, "Second instance of infocenter created!?!");
_instance = this;
_locationInfo = new LocationInfo();
_liveInfo = new LiveInfo();
_logInfo = new LogInfo();
_queueActions = new Vector();
_queueLivePoints = new Vector();
_state = STATE_STOPPED;
_nextWidgetPageRepaint = -1;
_nextBacklightRefresh = -1;
_hasBacklight = Util.hasBacklightSetting();
_live = new LiveClient();
_widgetPage = new WidgetPage();
_live.addListener(this);
_live.addLogger(new LiveLogger());
_keepsBacklight = Config.getKeepBacklight();
}
/****************************************************************************************
*
* Worker LOOP
*
*/
private void createConnection() {
if (Config.getProtocol() == Config.PROTOCOL_UDP)
_conn = new LiveUDPConnection(Config.getUDPServerHost(),Config.getUDPServerPort());
else if (Config.getProtocol() == Config.PROTOCOL_TCP)
_conn = new LiveTCPConnection(Config.getTCPServerHost(),Config.getTCPServerPort());
else
Util.expect(false, "createConnection() invalid connection type");
_conn.useZLib(Config.getUseZLib());
_conn.setResendInterval(Config.getResendInterval());
_conn.setReceiveReconnectInterval(Config.getReceiveReconnectInterval());
_live.setConnection(_conn);
}
private void ping() {
int pingMode = Config.getHTTPPingMode();
if (pingMode == Config.HTTP_PING_ONCE) {
new Thread(){
public void run() {
try {
Log.debug("HTTP Ping: start");
StreamConnection conn = (StreamConnection)Connector.open("http://live.xcontest.org");
InputStream is = conn.openInputStream();
int cnt = 0;
while (is.read() != -1) cnt ++;
Log.debug("HTTP Ping: request finished - received "+cnt+" bytes");
}
catch (IOException e) {
Log.error("HTTP Ping failed",e);
Util.showInfo("HTTP Ping failed: "+e.getMessage());
}
}
}.start();
}
}
public void run() {
Log.info("InfoCenter: started");
try {
while (true) {
// deliver new points to Live tracker
GpsPoint livePoint;
if (_state != STATE_STOPPED) {
while(true) {
synchronized(this) {
if (_queueLivePoints.size() == 0) break;
livePoint = (GpsPoint)_queueLivePoints.elementAt(0);
_queueLivePoints.removeElementAt(0);
}
if (_state == STATE_LOGIN || _state == STATE_TRACKING) {
_live.addPoint(livePoint);
}
_liveInfo.setPendingPoints(_live.getPendingPoints());
}
_live.checkSendPositionMessage();
}
// process ACTIONS
Profile activeProfile;
int action;
while(true) {
synchronized(this) {
if (_queueActions.size() == 0) break;
action = ((Integer)_queueActions.elementAt(0)).intValue();
activeProfile = _activeProfile;
_queueActions.removeElementAt(0);
}
// STOP TRACKING
if (action == ACTION_STOP_TRACKING) {
if (_state != STATE_STOPPED) {
Log.info("STOP Tracking");
_liveInfo.setClosedSession();
_gpsDriver.disconnect();
_gpsDriver.setListener(null);
_live.closeSession();
App.hideScreen(_widgetPage);
synchronized(this) {
_state = STATE_STOPPED;
}
}
}
// LOGIN OK
else if (action == ACTION_LOGIN_OK) {
synchronized(this) {
if (_activeProfile.isAnonymous())
_liveInfo.setAnonymousSession(_activeProfile.getNickname());
else
_liveInfo.setRegisteredSession(_activeProfile.getUsername(), _activeProfile.getDomain());
_state = STATE_TRACKING;
}
}
// LOGIN FAILED
else if (action == ACTION_LOGIN_FAILED) {
_liveInfo.setClosedSession();
_gpsDriver.disconnect();
_gpsDriver.setListener(null);
_live.closeSession();
App.hideScreen(_widgetPage);
Util.showError("Login to server FAILED!\nPlease go to Settings -> Profiles and correct your username/password settings");
synchronized(this) {
_state = STATE_STOPPED;
}
}
// START/CONTINUE TRACKING
else if (action == ACTION_START_TRACKING || action == ACTION_CONTINUE_TRACKING) {
if (_state == STATE_STOPPED) {
ping();
_locationInfo.reset();
_liveInfo.reset();
_liveInfo.startTracking();
synchronized(this) {
_nextLivePointTime = 0;
_state = STATE_LOGIN;
_queueLivePoints.removeAllElements();
}
_widgetPage.show();
createConnection();
GpsDeviceInfo dev = Config.getGpsDevice();
_gpsDriver = dev.getDriver();
_gpsDriver.setListener(this);
_gpsDriver.connect(dev.getAddress());
_live.setMessageInterval(activeProfile.getMessageInterval());
if (action == ACTION_START_TRACKING) {
_live.setPublic(true);
_live.setTrackType(_gpsDriver.isForDebugModeOnly() ? TrackType.DEMO.getValue() : activeProfile.getTrackType());
if (activeProfile.isAnonymous())
_live.openAnonymousSession(activeProfile.getFirstname(), activeProfile.getSurname(), activeProfile.getNickname());
else
_live.openRegisteredSession(activeProfile.getDomain(), activeProfile.getUsername(), activeProfile.getPassword());
}
else {
_live.continueSession(Config.getLastTrackKey());
}
}
}
// update LIVE STATUS
else if (action == ACTION_UPDATE_LIVE_STATUS) {
if (_state != STATE_STOPPED) {
_liveInfo.setBytesReceived(_conn.getTotalReceivedBytes());
_liveInfo.setBytesSent(_conn.getTotalSentBytes());
_liveInfo.setPendingMessages(_live.getPendingMessages());
_liveInfo.setConfirmedMessages(_live.getConfirmedMessages());
_liveInfo.setPendingPoints(_live.getPendingPoints());
_liveInfo.setConfirmedPoints(_live.getConfirmedPoints());
}
}
else if (action == ACTION_BACKLIGHT_ON) {
_keepsBacklight = true;
}
else if (action == ACTION_BACKLIGHT_OFF) {
_keepsBacklight = false;
}
// EXIT
else if (action == ACTION_EXIT) {
break;
}
} // process actions - while
long now = System.currentTimeMillis();
long wakeUp=now+10000;
if (_state != STATE_STOPPED) {
if (_hasBacklight && _keepsBacklight && _nextBacklightRefresh <= now) {
Util.setBacklight(Config.getBacklightLevel());
_nextBacklightRefresh = now+BACKLIGHT_REFRESH_INTERVAL;
}
if (_nextWidgetPageRepaint <= now) {
int repaint = (int)(1000*Config.getWidgetPageRepaintInterval());
int minwait = repaint/10;
_nextWidgetPageRepaint += repaint;
if (_nextWidgetPageRepaint < now+minwait)
_nextWidgetPageRepaint = now+minwait;
_widgetPage.doFullRepaint();
}
if (wakeUp > _nextBacklightRefresh)
wakeUp = _nextBacklightRefresh;
if (wakeUp > _nextWidgetPageRepaint)
wakeUp = _nextWidgetPageRepaint;
}
long delay = wakeUp - System.currentTimeMillis();
if (delay > 0) {
synchronized(this) {
wait(delay);
}
}
}
}
catch(InterruptedException e) {
Log.error("InfoCenter: Thread interrupted - exiting");
}
catch(Throwable e) {
Log.error("InfoCenter: FATAL",e);
}
Log.error("InfoCenter: Thread finished normally - exiting");
}
}