/* * ShareNav - Copyright (c) 2009 Kai Krueger apmonkey at users dot sourceforge dot net * * 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 2 * 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. * * See COPYING */ package net.sharenav.gps.location; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordStore; //#if polish.api.online import net.sharenav.util.HttpHelper; //#endif import net.sharenav.midlet.ui.UploadListener; import net.sharenav.util.IntTree; import net.sharenav.util.Logger; import net.sharenav.util.StringTokenizer; import net.sharenav.sharenav.data.Configuration; import net.sharenav.sharenav.data.Position; import net.sharenav.sharenav.ui.ShareNav; import de.enough.polish.util.Locale; /** * * This location provider tries to use the cell-id of the currently * connected cell to retrieve a very rough estimate of position. This * estimate can be off by up to the range of kilometers. In order to * map the cell-id to a location we use OpenCellID.org, that uses * crowd sourcing to determine the locations. As such, many cell-ids * may not yet be in their database. * * This LocationProvider can only retrieve cell-ids for Sony Ericsson phones * */ public class SECellId implements LocationMsgProducer, UploadListener { private static final byte CELLDB_LACIDX = 1; private static final byte CELLDB_LACLIST = 2; private static final byte CELLDB_VERSION = 1; private static final String CELLDB_NAME = "ShareNav-CellIds"; private CellIdProvider cellProvider; /** * This object is an index entry to list in which * RecordStore entry the information for a given * (mcc,mnc,lac) area is in. * * */ public final static class LacIdxEntry { public short mcc; public short mnc; public int lac; public int recordId; public LacIdxEntry() { // TODO Auto-generated constructor stub } public LacIdxEntry (DataInputStream dis) throws IOException { mcc = dis.readShort(); mnc = dis.readShort(); lac = dis.readInt(); recordId = dis.readInt(); } public void serialize(DataOutputStream dos) throws IOException { dos.writeShort(mcc); dos.writeShort(mnc); dos.writeInt(lac); dos.writeInt(recordId); } public int hashCode(short mcc, short mnc, int lac) { return lac + (mnc << 16) + (mcc << 23); } public int hashCode() { return hashCode(mcc,mnc,lac); } public String toString() { return Locale.get("secellid.LacIdxEntry")/* LacIdxEntry (mcc=*/ + mcc + ", mnc=" + mnc + ", lac=" + lac + " -> " + recordId + " |" + hashCode() + "|)"; } } public void triggerLastKnownPositionUpdate() { } public void triggerPositionUpdate() { if (rp == null) { rp = new RetrievePosition(); } rp.run(); } /** * Periodically retrieve the current Cell-id and * convert cell id to a location and send it * to the LocationReceiver * */ public class RetrievePosition extends TimerTask { public void run() { GsmCell loc; GsmCell cellLoc = null; try { if (closed) { this.cancel(); return; } cellLoc = cellProvider.obtainCurrentCellId(); if ((cellLoc == null) || (cellLoc.mcc == 0)) { /** * This can either be the case because * we currently don't have cell coverage, * or because the phone doesn't support this * feature. Return a not-connected solution * to indicate this */ //#debug debug logger.debug("No valid cell-id available"); receiverList.receiveStatus(LocationMsgReceiver.STATUS_NOFIX, 0); return; } /** * Check if we have the cell ID already cached */ loc = retrieveFromCache(cellLoc); if (loc == null) { //#debug debug logger.debug(cellLoc + " was not in cache, retrieving from FS cache"); if (Configuration.getCfgBitState(Configuration.CFGBIT_CELLID_ONLINEONLY)) { loc = null; } else { loc = retrieveFromFS(cellLoc); } if (loc == null) { //#debug debug logger.debug(cellLoc + " was not in FS cache, retrieving from persistent cache"); if (Configuration.getCfgBitState(Configuration.CFGBIT_CELLID_ONLINEONLY)) { loc = null; } else { loc = retrieveFromPersistentCache(cellLoc); } if (loc == null) { //#if polish.api.online //#debug info logger.info(cellLoc + " was not in persistent cache, retrieving from OpenCellId.org"); if (! Configuration.getCfgBitState(Configuration.CFGBIT_CELLID_OFFLINEONLY)) { loc = retrieveFromOpenCellId(cellLoc); } //#endif if (loc != null) { cellPos.put(loc.cellID, loc); if ((loc.lat != 0.0) || (loc.lon != 0.0)) { storeCellIDtoRecordStore(loc); } else { //#debug debug logger.debug("Not storing cell, as it has no valid coordinates"); } } else { //#debug info logger.info("Cell is unknown, can't calculate a location based on it"); receiverList.receiveStatus(LocationMsgReceiver.STATUS_NOFIX, 0); return; } } } else { cellPos.put(loc.cellID, loc); } } if (rawDataLogger != null) { String logStr = "Cell-id: " + loc.cellID + " mcc: " + loc.mcc + " mnc: " + loc.mnc + " lac: " + loc.lac + " --> " + loc.lat + " | " + loc.lon; rawDataLogger.write(logStr.getBytes()); rawDataLogger.flush(); } if ((loc.lat != 0.0) && (loc.lon != 0.0)) { if (receiverList == null) { logger.error(Locale.get("secellid.ReceiverListNull")/*ReceiverList == null*/); } //#debug info logger.info("Obtained a position from " + loc); receiverList.receiveStatus(LocationMsgReceiver.STATUS_CELLID, 0); receiverList.receivePosition(new Position(loc.lat, loc.lon, 0, 0, 0, 0, System.currentTimeMillis(), Position.TYPE_CELLID)); } else { receiverList.receiveStatus(LocationMsgReceiver.STATUS_NOFIX, 0); } } catch (Exception e) { logger.silentexception("Could not retrieve cell-id", e); this.cancel(); close(Locale.get("secellid.AlCellIDretfail")/*Cell-id retrieval failed*/); } } } private static final Logger logger = Logger.getInstance(SECellId.class, Logger.TRACE); protected OutputStream rawDataLogger; protected Thread processorThread; protected final LocationMsgReceiverList receiverList = new LocationMsgReceiverList(); protected boolean closed = false; private String message; private RetrievePosition rp = null; private IntTree cellPos; private IntTree lacidx; private int dblacidx; private static boolean retrieving; private static boolean retrieved; public SECellId() { } public static void deleteCellIDRecordStore() { try { //#debug info logger.info("deleting cellID recordstore to clear cell cache"); RecordStore.deleteRecordStore(CELLDB_NAME); } catch (Exception e) { logger.exception(Locale.get("secellid.ExCellIDCachClearFail")/*Failed to delete cell-id to clear persistent cache*/, e); } } public boolean init(LocationMsgReceiver receiver) { try { this.receiverList.addReceiver(receiver); cellPos = new IntTree(); lacidx = new IntTree(); cellProvider = CellIdProvider.getInstance(); if (cellProvider.obtainCurrentCellId() == null) { //#debug info logger.error("No valid cell-id, closing down"); this.receiverList.locationDecoderEnd(Locale.get("secellid.AlNoValidCellID")/*No valid cell-id*/); return false; } closed = false; //#debug info logger.info("Opening persistent Cell-id database"); RecordStore db = RecordStore.openRecordStore(CELLDB_NAME, true); if (db.getNumRecords() > 0){ /** * Find the record store entry containing the index * mapping (mcc, mnc, lac) to a recordstore entry with the * list of corresponding cells */ try { boolean indexFound = false; RecordEnumeration re = db.enumerateRecords(null, null, false); while (!indexFound) { if (!re.hasNextElement()) { throw new IOException(Locale.get("secellid.ExCellDBIdxFail")/*Failed to find index for Cell-id database*/); } dblacidx = re.nextRecordId(); byte [] buf = db.getRecord(dblacidx); DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf)); if (dis.readByte() == CELLDB_LACIDX) { if (dis.readByte() != CELLDB_VERSION) { throw new IOException(Locale.get("secellid.ErCellDBVersionMismatch")/*Wrong version of CellDb, expected */ + CELLDB_VERSION); } int size = dis.readInt(); //#debug info logger.info("Found valid lacidx with " + size + " entries"); for (int i = 0; i < size; i++) { //#debug debug logger.debug("Reading lac entry " + i + " of " + size); LacIdxEntry idxEntry = new LacIdxEntry(dis); lacidx.put(idxEntry.hashCode(), idxEntry); //#debug debug logger.debug("Adding index entry for " + idxEntry); } if (dis.readInt() != 0xbeafdead) { throw new IOException(Locale.get("secellid.ErCellPersIdxFail")/*Persistent cell-id index is corrupt*/); } indexFound = true; } else { //ignore other types of record entries, as we are currently only interested //in the index entry } } } catch (IOException ioe) { logger.exception(Locale.get("secellid.ExCellcacheDropping")/*Could not read persistent cell-id cache. Dropping to recover*/, ioe); db.closeRecordStore(); RecordStore.deleteRecordStore(CELLDB_NAME); db = RecordStore.openRecordStore(CELLDB_NAME, true); } } if (db.getNumRecords() == 0) { logger.info("Persistent Cell-id database is empty, initialising it"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeByte(CELLDB_LACIDX); dos.writeByte(CELLDB_VERSION); dos.writeInt(0); dos.writeInt(0xbeafdead); dos.flush(); dblacidx = db.addRecord(baos.toByteArray(), 0, baos.size()); } db.closeRecordStore(); return true; } catch (SecurityException se) { logger.silentexception( "Do not have permission to retrieve cell-id", se); } catch (Exception e) { logger.silentexception("Could not retrieve cell-id", e); } this.receiverList.locationDecoderEnd(Locale.get("secellid.AlCellIDLocFail")/*Cant use Cell-id for location*/); return false; } public boolean activate(LocationMsgReceiver receiver) { rp = new RetrievePosition(); ShareNav.getTimer().schedule(rp, 1000, 5000); return true; } public boolean deactivate(LocationMsgReceiver receiver) { return true; } private GsmCell retrieveFromCache(GsmCell cell) { GsmCell loc = (GsmCell) cellPos.get(cell.cellID); if ((loc != null) && (loc.lac == cell.lac) && (loc.mcc == cell.mcc) && (loc.mnc == cell.mnc)) { //#debug debug logger.debug("Found a valid cached cell: " + loc); return loc; } else { return null; } } //#if polish.api.online private synchronized GsmCell retrieveFromOpenCellId(GsmCell cellLoc) { GsmCell loc = null; if (retrieving) { logger.info("Still retrieving previous ID"); return null; } retrieving = true; retrieved = false; /** * Connect to the Internet and retrieve location information * for the current cell-id from OpenCellId.org */ String url = "http://www.opencellid.org/cell/get?mcc=" + cellLoc.mcc + "&mnc=" + cellLoc.mnc + "&cellid=" + cellLoc.cellID + "&lac=" + cellLoc.lac + "&fmt=txt"; HttpHelper http = new HttpHelper(); http.getURL(url, this); try { if (!retrieved) { wait(); } } catch (InterruptedException ie) { retrieving = false; return loc; } String str = http.getData(); if (str == null) { retrieving = false; return loc; } //#debug debug logger.debug("Cell-ID retrieval: " + str); String[] pos = StringTokenizer.getArray(str,","); float lat = Float.parseFloat(pos[0]); float lon = Float.parseFloat(pos[1]); int accuracy = Integer.parseInt(pos[2]); loc = new GsmCell(); loc.cellID = cellLoc.cellID; loc.mcc = cellLoc.mcc; loc.mnc = cellLoc.mnc; loc.lac = cellLoc.lac; loc.lat = lat; loc.lon = lon; if (cellPos == null) { logger.error(Locale.get("secellid.CellposNull")/*Cellpos == null*/); retrieving = false; return null; } retrieving = false; return loc; } //#endif private GsmCell retrieveFromFS(GsmCell cellLoc) { GsmCell ret; String filename = "/c" + cellLoc.mcc + cellLoc.mnc + cellLoc.lac + ".id"; InputStream is ; try { // assuming here 0 for LAC is not valid; don't try to open the db for an invalid LAC if (cellLoc.lac == 0) { throw new IOException("LAC == 0"); } is = Configuration.getMapResource(filename); } catch (IOException ioe) { //#debug debug logger.debug("Could not find Operator CellID file " + filename); try { /** * In order to reduce the number of cells, we combine all the lacs that * have less than 20 cells in them into a single file */ filename = "/c" + cellLoc.mcc + cellLoc.mnc +".id"; is = Configuration.getMapResource(filename); } catch (IOException ioe2) { //#debug debug logger.debug("Could not find file " + filename + " either"); return null; } } try { if (is != null) { DataInputStream dis = new DataInputStream(is); int noCellsRead = 0; while (dis.available() > 0) { noCellsRead++; int cellLAC = dis.readInt(); int cellID = dis.readInt(); float lat = dis.readFloat(); float lon = dis.readFloat(); if ((cellLoc.lac == 0 || cellLAC == cellLoc.lac) && (cellID == cellLoc.cellID)) { ret = new GsmCell(); ret.mcc = cellLoc.mcc; ret.mnc = cellLoc.mnc; ret.lac = cellLoc.lac; ret.cellID = cellID; ret.lat = lat; ret.lon = lon; //#debug debug logger.debug("Found Cell in FS cache " + ret); return ret; } } //#debug debug logger.debug("Read " + noCellsRead + " Cells, but not the one we are looking for"); } } catch (IOException ioe) { ioe.printStackTrace(); } return null; } private GsmCell retrieveFromPersistentCache(GsmCell cell) { //#debug info logger.info("Looking for " + cell + " in persistent cache"); try { RecordStore db = RecordStore.openRecordStore(CELLDB_NAME, false); LacIdxEntry idx = new LacIdxEntry(); idx = (LacIdxEntry) lacidx.get(idx.hashCode(cell.mcc, cell.mnc, cell.lac)); if (idx == null) { return null; } else { /** * Load the entries for the current area from the * record store db into the cache; */ byte [] buf = db.getRecord(idx.recordId); DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf)); if (dis.readByte() == CELLDB_LACLIST) { int size = dis.readInt(); for (int i = 0; i < size; i++) { GsmCell tmpCell = new GsmCell(dis); //#debug debug logger.debug("Adding " + tmpCell + " to cache from persistent store " + idx); cellPos.put(tmpCell.cellID, tmpCell); } if (dis.readInt() != 0xdeadbeaf) { logger.error(Locale.get("secellid.ErCellcacheCorrupt2")/*Persistent Cell-id cache is corrupt*/); } } else { logger.error(Locale.get("secellid.ErCellcacheCorrupt2")/*Persistent Cell-id cache is corrupt*/); } } db.closeRecordStore(); /** * The entry should now be in the cache, so * retrieve it and return it */ return retrieveFromCache(cell); } catch (Exception e) { logger.exception(Locale.get("secellid.FailedToLookFor")/*Failed to look for */ + cell + Locale.get("secellid.inPersistentCache")/* in persistent cache*/, e); } return null; } private void storeCellIDtoRecordStore(GsmCell cell) { try { //#debug info logger.info("Storing " + cell + " in persistent cell cache"); RecordStore db = RecordStore.openRecordStore(CELLDB_NAME, false); LacIdxEntry idx = new LacIdxEntry(); idx = (LacIdxEntry) lacidx.get(idx.hashCode(cell.mcc, cell.mnc, cell.lac)); if (idx == null) { //#debug debug logger.debug("First cell in this area"); idx = new LacIdxEntry(); idx.lac = cell.lac; idx.mcc = cell.mcc; idx.mnc = cell.mnc; /** * Writing the cell to its area entry */ ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeByte(CELLDB_LACLIST); dos.writeInt(1); //Size cell.serialise(dos); dos.writeInt(0xdeadbeaf); dos.flush(); idx.recordId = db.addRecord(baos.toByteArray(), 0, baos.size()); dos.close(); dos = null; lacidx.put(idx.hashCode(), idx); /** * Adding area to the area index */ byte [] buf = db.getRecord(dblacidx); DataInputStream dis = new DataInputStream(new ByteArrayInputStream(buf)); baos = new ByteArrayOutputStream(); dos = new DataOutputStream(baos); if (dis.readByte() == CELLDB_LACIDX) { if (dis.readByte() != CELLDB_VERSION) { logger.error(Locale.get("secellid.ErCellDBVersionMismatch")/*Wrong version of CellDb, expected */ + CELLDB_VERSION); db.closeRecordStore(); return; } dos.writeByte(CELLDB_LACIDX); dos.writeByte(CELLDB_VERSION); int size = dis.readInt(); dos.writeInt(size + 1); for (int i = 0; i < size; i++) { LacIdxEntry lie = new LacIdxEntry(dis); lie.serialize(dos); } if (dis.readInt() != 0xbeafdead) { logger.error(Locale.get("secellid.ErCellPersIdxFail")/*Persistent cell-id index is corrupt*/); } idx.serialize(dos); dos.writeInt(0xbeafdead); dos.flush(); db.setRecord(dblacidx, baos.toByteArray(), 0, baos.size()); } else { logger.error(Locale.get("secellid.ErrCellDBreadCorrupt")/*Corrupted read of Cell-id db*/); } db.closeRecordStore(); } else { /** * There is already a cell in this area, so add it to the * correct entry. */ //#debug debug logger.debug("Adding " + cell + " to " + idx); byte [] buf = db.getRecord(idx.recordId); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); DataInputStream dis = new DataInputStream( new ByteArrayInputStream(buf)); if (dis.readByte() == CELLDB_LACLIST) { dos.writeByte(CELLDB_LACLIST); int size = dis.readInt(); dos.writeInt(size + 1); for (int i = 0; i < size; i++) { GsmCell tmpCell = new GsmCell(dis); tmpCell.serialise(dos); } if (dis.readInt() != 0xdeadbeaf) { logger.error(Locale.get("secellid.ErCellDBPersCorrupt1")/*Persistent Cellid-cache is corrupt*/); } cell.serialise(dos); dos.writeInt(0xdeadbeaf); dos.flush(); db.setRecord(idx.recordId, baos.toByteArray(), 0, baos.size()); //#debug debug logger.debug("Added Cell to area list"); } else { logger.error(Locale.get("secellid.ErCellDBPersCorrupt1")/*Persistent Cellid-cache is corrupt*/); } } } catch (Exception e) { logger.exception(Locale.get("secellid.ExSaveCellPersFail")/*Failed to save cell-id to persistent cache*/, e); } } public void close() { logger.info("Location producer closing"); closed = true; if (processorThread != null) { processorThread.interrupt(); } receiverList.locationDecoderEnd(); } public void close(String message) { this.message = message; close(); } public void enableRawLogging(OutputStream os) { rawDataLogger = os; } public void disableRawLogging() { if (rawDataLogger != null) { try { rawDataLogger.close(); } catch (IOException e) { logger.exception(Locale.get("secellid.ExCloseGPSLogFail")/*Couldnt close raw GPS logger*/, e); } rawDataLogger = null; } } public void addLocationMsgReceiver(LocationMsgReceiver receiver) { receiverList.addReceiver(receiver); } public boolean removeLocationMsgReceiver(LocationMsgReceiver receiver) { return receiverList.removeReceiver(receiver); } public synchronized void completedUpload(boolean success, String message) { logger.info("Download of cellid completed!"); retrieved = true; notifyAll(); } public void setProgress(String message) { // TODO Auto-generated method stub } public void startProgress(String title) { // TODO Auto-generated method stub } public void updateProgress(String message) { // TODO Auto-generated method stub } public void updateProgressValue(int increment) { // TODO Auto-generated method stub } public void uploadAborted() { // TODO Auto-generated method stub } }