package org.fitchfamily.android.gsmlocation.database; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.util.Log; import org.fitchfamily.android.gsmlocation.Config; import org.fitchfamily.android.gsmlocation.Settings; import static org.fitchfamily.android.gsmlocation.LogUtils.makeLogTag; public class CellLocationDatabase { private static final String TAG = makeLogTag("database"); private static final boolean DEBUG = Config.DEBUG; private static final String TABLE_CELLS = "cells"; private static final String COL_LATITUDE = "latitude"; private static final String COL_LONGITUDE = "longitude"; private static final String COL_ACCURACY = "accuracy"; private static final String COL_SAMPLES = "samples"; private static final String COL_MCC = "mcc"; private static final String COL_MNC = "mnc"; private static final String COL_LAC = "lac"; private static final String COL_CID = "cid"; private static final String[] COLUMNS = new String[] { COL_LATITUDE, COL_LONGITUDE, COL_ACCURACY, COL_SAMPLES, COL_MCC, COL_MNC, COL_LAC, COL_CID }; // the indexes are always the same because we always use the COLUMNS array private static final int INDEX_LATITUDE = 0; private static final int INDEX_LONGITUDE = 1; private static final int INDEX_ACCURACY = 2; private static final int INDEX_SAMPLES = 3; private static final int INDEX_MCC = 4; private static final int INDEX_MNC = 5; private static final int INDEX_LAC = 6; private static final int INDEX_CID = 7; private SQLiteDatabase database; private QueryCache queryCache = new QueryCache(); private Settings settings; public CellLocationDatabase(Context context) { settings = Settings.with(context); } public void checkForNewDatabase() { if (settings.newDatabaseFile().exists() && settings.newDatabaseFile().canRead()) { if (DEBUG) { Log.i(TAG, "New database file detected."); } if (database != null) database.close(); database = null; settings.currentDatabaseFile().renameTo(settings.bakDatabaseFile()); settings.newDatabaseFile().renameTo(settings.currentDatabaseFile()); } } private void openDatabase() { if (database == null) { if (DEBUG) { Log.i(TAG, "Attempting to open database."); Log.i(TAG, "Using directory " + settings.databaseDirectory().getPath()); } if (settings.currentDatabaseFile().exists() && settings.currentDatabaseFile().canRead()) { try { database = SQLiteDatabase.openDatabase(settings.currentDatabaseFile().getAbsolutePath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error opening database: "+ e.getMessage()); } database = null; settings.currentDatabaseFile().delete(); if (settings.bakDatabaseFile().exists() && settings.bakDatabaseFile().canRead()) { if (DEBUG) { Log.e(TAG, "Reverting to old database"); } settings.bakDatabaseFile().renameTo(settings.currentDatabaseFile()); openDatabase(); } } } else { if (DEBUG) { Log.e(TAG, "Unable to open database " + settings.currentDatabaseFile()); } database = null; } } } public synchronized Location query(final Integer mcc, final Integer mnc, final int cid, final int lac) { SqlWhereBuilder queryBuilder = new SqlWhereBuilder(); // short circuit duplicate calls QueryArgs args = new QueryArgs(mcc, mnc, cid, lac); if(queryCache.contains(args)) { return queryCache.get(args); } openDatabase(); if (database == null) { if (DEBUG) { Log.i(TAG, "Unable to open cell tower database file."); } return null; } // Build up where clause and arguments based on what we were passed if (mcc != null) { queryBuilder .columnIs(COL_MCC, String.valueOf(mcc)) .and(); } if (mnc != null) { queryBuilder .columnIs(COL_MNC, String.valueOf(mnc)) .and(); } queryBuilder .columnIs(COL_LAC, String.valueOf(lac)) .and() .columnIs(COL_CID, String.valueOf(cid)); Cursor cursor = database.query(TABLE_CELLS, COLUMNS, queryBuilder.selection(), queryBuilder.selectionArgs(), null, null, null); try { if (cursor != null) { if (cursor.getCount() > 0) { LocationCalculator locationCalculator = new LocationCalculator(); // Get weighted average of tower locations and coverage // range from reports by the various providers (OpenCellID, // Mozilla location services, etc.) while (!cursor.isLast()) { cursor.moveToNext(); int db_mcc = cursor.getInt(INDEX_MCC); int db_mnc = cursor.getInt(INDEX_MNC); int db_lac = cursor.getInt(INDEX_LAC); int db_cid = cursor.getInt(INDEX_CID); double thisLat = cursor.getDouble(INDEX_LATITUDE); double thisLng = cursor.getDouble(INDEX_LONGITUDE); double thisRng = cursor.getDouble(INDEX_ACCURACY); int thisSamples = cursor.getInt(INDEX_SAMPLES); if (DEBUG) { Log.i(TAG, "query result: " + db_mcc + ", " + db_mnc + ", " + db_lac + ", " + db_cid + ", " + thisLat + ", " + thisLng + ", " + thisRng + ", " + thisSamples); } locationCalculator.add(thisLat, thisLng, thisSamples, thisRng); } if (DEBUG) { Log.i(TAG, "Final result: " + locationCalculator); } Location cellLocInfo = locationCalculator.toLocation(); queryCache.put(args, cellLocInfo); if (DEBUG) { Log.i(TAG, "Cell info found: " + args.toString()); } return cellLocInfo; } else { Log.i(TAG, "DB Cursor empty for: " + args.toString()); queryCache.putUnresolved(args); } } else { Log.i(TAG, "DB Cursor null for: " + args.toString()); queryCache.putUnresolved(args); } return null; } finally { if(cursor != null) { cursor.close(); } } } }