/** * */ package com.geeksville.location; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Random; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.SystemClock; import android.util.Log; import com.flurry.android.FlurryAgent; /** * Writes tracks to the Paragliding (or other) Lenoardo Live server * * @author kevinh * * For a spec see: * http://www.livetrack24.com/wiki/index.php?title=Leonardo * %20Live%20Tracking%20API&mode=print&lang=en Supposedly they also have * a UDP based API, which we should probably switch to at some point. * * FIXME - validate user input to quote & etc... */ public class LeonardoLiveWriter implements PositionWriter { /** * Sent to server */ private String ourVersion; /** * How many secs between position reports - FIXME, choose correctly */ private int expectedIntervalSecs; /** * FIXME, get the real phone name */ private String phoneName = android.os.Build.MODEL; /** * Our app name, FIXME - pick a good name */ private String programName = "Android Gaggle"; /** * FIXME - pass in from app */ private String vehicleName; /** * Hardwired for paraglider - FIXME */ private int vehicleType = 1; /** * Claim built in for now */ private String gpsType = "Internal GPS"; private String userName; private String password; private String trackURL, clientURL; private int sessionId = (new Random()).nextInt(0x7fffffff); private int packetNum = 1; /** * Constructor * * @param serverURL * The server we are using, www.livetrack24.com/track.php for * real server, test.livetrack24.com for test * @param userName * The user login name on the site (low security) * @param password * The user password * @throws Exception */ public LeonardoLiveWriter(Context context, String serverURL, String userName, String password, String vehicleName, int vehicleType, int expectedInterval) throws Exception { PackageManager pm = context.getPackageManager(); PackageInfo pi; try { pi = pm.getPackageInfo(context.getPackageName(), 0); ourVersion = pi.versionName; } catch (NameNotFoundException eNnf) { throw new RuntimeException(eNnf); // We better be able to find the // info about our own package } URL url = new URL(serverURL + "/track.php"); trackURL = url.toString(); url = new URL(serverURL + "/client.php"); clientURL = url.toString(); this.userName = userName; this.password = password; this.vehicleType = vehicleType; this.vehicleName = vehicleName; expectedIntervalSecs = expectedInterval; doLogin(); // Login here, so we can find out about bad passwords ASAP } static int PACKET_START = 2; // FIXME, lookup java const syntax static int PACKET_END = 3; static int PACKET_POINT = 4; /** * Cleans up illegal chars in a URL * * @param url * @return FIXME move */ static String normalizeURL(String url) { return url.replace(' ', '+'); // FIXME, do a better job of this } /** * send the packet up to the server * * @param packetType * Used to construct leolive code * @param options * @throws IOException */ void sendPacket(int packetType, String options) throws IOException { try { String urlstr = String.format(Locale.US, "%s?leolive=%d&sid=%d&pid=%d&%s", trackURL, packetType, sessionId, packetNum, options); URL url = new URL(normalizeURL(urlstr)); url.openStream().close(); } catch (MalformedURLException ex) { // We should have caught this in the constructor throw new RuntimeException(ex); } packetNum++; } /** * @see com.geeksville.location.PositionWriter#emitEpilog() */ @Override public void emitEpilog() { try { // FIXME - add support for end of track types (need retrieve etc...) sendPacket(PACKET_END, "prid=0"); } catch (IOException ex) { System.out.println("Ignoring on epilog: " + ex); } } long lastUpdateTime = SystemClock.elapsedRealtime(); /** * @see com.geeksville.location.PositionWriter#emitPosition(long, double, * double, float, int, float, float[], float) */ @Override public void emitPosition(long time, double latitude, double longitude, float altitude, int bearing, float groundSpeed, float[] accel, float vspd) { try { int groundKmPerHr = (int) groundSpeed; int unixTimestamp = (int) (time / 1000); // Convert from msecs to // secs long now = SystemClock.elapsedRealtime(); if (lastUpdateTime + (expectedIntervalSecs *1000) < now) { String opts = String.format(Locale.US, "lat=%f&lon=%f&alt=%d&sog=%d&cog=%d&tm=%d", latitude, longitude, Float.isNaN(altitude) ? 0 : (int) altitude, groundKmPerHr, bearing, unixTimestamp); Log.d("XXX", opts); sendPacket(PACKET_POINT, opts); lastUpdateTime = SystemClock.elapsedRealtime(); } } catch (IOException ex) { System.out.println("Ignoring on epilog: " + ex); } } private void doLogin() throws Exception { // If the user has an account on the server then the sessionID must be // constructed in the following way to contain the userID // First of all your application must get the userID based on the // username and password of the user. The url to verify user accounts // and get back the userID is // http://www.livetrack24.com/client.php?op=login&user=username&pass=pass // The username and password are case INSENSITIVE, because on mobile // devices it is not easy for all users to enter the correct case. // The result of the page is an integer, 0 if userdata are incorrect, or // else the userID of the user String urlstr = String.format("%s?op=login&user=%s&pass=%s", clientURL, userName, password); URL url = new URL(normalizeURL(urlstr)); InputStream responseStream = url.openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(responseStream)); String response = reader.readLine(); try { int userID = Integer.parseInt(response); if (userID == 0) throw new Exception("Invalid username or password"); Random a = new Random(System.currentTimeMillis()); int rnd = Math.abs(a.nextInt()); // we make an int with leftmost bit=1 , // the next 7 bits random // (so that the same userID can have multiple active sessions) // and the next 3 bytes the userID sessionId = (rnd & 0x7F000000) | (userID & 0x00ffffff) | 0x80000000; } catch (NumberFormatException ex) { throw new Exception("Unexpected server response"); } } /** * @see com.geeksville.location.PositionWriter#emitProlog(java.lang.String, * java.lang.String) */ @Override public void emitProlog() { try { String opts = String .format( Locale.US, "client=%s&v=%s&user=%s&pass=%s&phone=%s&gps=%s&trk1=%d&vtype=%d&vname=%s", programName, ourVersion, userName, password, phoneName, gpsType, expectedIntervalSecs, vehicleType, vehicleName); Log.d("XXX", opts); sendPacket(PACKET_START, opts); // Keep stats on # of live uploads Map<String, String> map = new HashMap<String, String>(); map.put("User", userName); map.put("Time", (new Date()).toGMTString()); FlurryAgent.onEvent("LiveUpload", map); } catch (IOException ex) { System.out.println("FIXME, rethrow on connect failed " + ex); } } }