public final class AIMSICDDbAdapter extends SQLiteOpenHelper {

    public static final int DATABASE_VERSION = 1; // Is this "pragma user_version;" ?

    public static final String TAG = "AICDL";
    public static final String mTAG = "DB: ";

    // TODO: This should be implemented as a SharedPreference...
    private static final Boolean MONO_DB_DUMP = true; // Also back-up DB with one monolithic dump file?
    private static final String DB_NAME = "aimsicd.db";

    private String[] mTables;
    private SQLiteDatabase mDb;
    private Context mContext;
    private SharedPreferences mPreferences;
    public static String mDatabasePath;
    public static String mExternalFilesDirPath;

    public AIMSICDDbAdapter(Context context) {
        super(context, DB_NAME, null, 1);
        mContext = context;
        mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        mDatabasePath = mContext.getDatabasePath(DB_NAME).getAbsolutePath();
        mExternalFilesDirPath = mContext.getExternalFilesDir(null) + File.separator;
        //e.g. /storage/emulated/0/Android/data/zz.aimsicd.lite/

        // Create a new blank DB then write pre-compiled DB in assets folder to blank DB.
        // This will throw error on first create because there is no DB to open and this is normal.
        createDataBase();

        //return writable database
        mDb = SQLiteDatabase.openDatabase(mDatabasePath, null, SQLiteDatabase.OPEN_READWRITE);

        // This will return the database as open so we don't need to use .open . Then when app
        // is exiting we use new AIMSICDDbAdapter(getApplicationContext()).close(); to close it
        this.getWritableDatabase();
        mTables = new String[]{

                // I am trying to keep in same order and aimsicd.sql script
                // Only backing up useful tables, uncomment if you want to backup
                DBTableColumnIds.DEFAULT_LOCATION_TABLE_NAME,   // defaultLocation:     Default MCC for each country
                //DBTableColumnIds.API_KEYS_TABLE_NAME,         // API_keys:            API keys for OpenCellID, MLS etc.
                //DBTableColumnIds.COUNTER_MEASURES_TABLE_NAME, // CounterMeasures:     Counter Measures thresholds and description
                //DBTableColumnIds.DBE_CAPABILITIES_TABLE_NAME, // DBe_capabilities:    External: MNO & BTS network capabilities
                DBTableColumnIds.DBE_IMPORT_TABLE_NAME,         // DBe_import:          External: BTS import table
                DBTableColumnIds.DBI_BTS_TABLE_NAME,            // DBi_bts:             Internal: (physical) BTS data
                DBTableColumnIds.DBI_MEASURE_TABLE_NAME,        // DBi_measure:         Internal: (volatile) network measurements
                //DBTableColumnIds.DETECTION_FLAGS_TABLE_NAME,  // DetectionFlags:      Detection Flag description, settings and scoring table
                DBTableColumnIds.EVENTLOG_TABLE_NAME,           //                      Detection and general EventLog (persistent)
                //DBTableColumnIds.SECTOR_TYPE_TABLE_NAME,      // SectorType:          BTS tower sector configuration (Many CID, same BTS)
                DBTableColumnIds.DETECTION_STRINGS_TABLE_NAME,  //                      Detection strings to will be picked up in logcat
                DBTableColumnIds.SMS_DATA_TABLE_NAME,           //                      Silent SMS details
        };
    }

    /**
     * Description:     Creates an empty SQLite Database file on the system and rewrites it with
     *                  our own pre-fabricated AIMSICD.db.
     *
     * NOTES:           This is a modified version to suit of needs of this guys great guide on
     *                  how to build a pre compiled db for android. Cheers Juan-Manuel Fluxà
     *                  See:
     *                  http://www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/
     *
     **/
    public boolean createDataBase() {
        if (!checkDataBase()) {
            // By calling this method, an empty database will be created into the default system path
            // of your application so that we can overwrite that database with our own database.
            this.getReadableDatabase();
            try {
                copyDataBase();
                Log.i(TAG, mTAG + "Database created");
                return true;
            } catch (IOException e) {
                Log.i(TAG, mTAG + "Error creating database: " + e);
                throw new Error("Error copying database", e);
            }

        }
        return false;
    }

    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     *
     * ToDo: NOTE:  This is a dumb check, as it currently only tries to open it.
     *              It may be other reasons why it can't be opened even if it already exists.
     *
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase() {

        SQLiteDatabase checkDB = null;
        try {
            Log.i(TAG, mTAG + "Checking if DB exists...");
            checkDB = SQLiteDatabase.openDatabase(mDatabasePath, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e) {
            Log.i(TAG, mTAG + "SQL Exception! Database can\'t be opened: " + e);
            //Log.i(TAG, mTAG + "Database not yet created: " + e);
        }

        if (checkDB != null) {
            checkDB.close();
            Log.i(TAG, mTAG + "OK (found)");
            return true;
        }
        Log.i(TAG, mTAG + "Database probably not yet created.");
        return false;
    }

    /**
     * Description:     Copies your database from your local assets-folder to the just created
     *                  empty database in the system folder, from where it can be accessed and handled.
     *                  This is done by transferring bytestream.
     */
    private void copyDataBase() throws IOException {
        // Open your local DB as the input stream
        InputStream myInput = mContext.getAssets().open(DB_NAME);
        // Open the empty DB as the output stream
        OutputStream myOutput = new FileOutputStream(mDatabasePath);

        // Transfer bytes from the input file to the output file
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer)) > 0) {
            myOutput.write(buffer, 0, length);
        }
        // Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
    }

    public AIMSICDDbAdapter open() throws SQLException {
        mDb = this.getWritableDatabase();
        return this;
    }

    public void close() {
        mDb.close();
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // To use foreign keys in SQLite we use:
        //db.execSQL("PRAGMA foreign_keys = ON;");
        // But this is already created inside SQL script!!
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
    }


    // ====================================================================
    //      Populate the DB tables  (INSERT)
    // ====================================================================

    /**
     * Description:     This is used in the AIMSICD framework Tests to delete cells.
     *                  see: ../src/androidTest/java/com.SecUpwN.test/.
     *
     * Issues:          TODO: See comments below!
     *
     * @param cellId    This method deletes a cell with CID from CELL_TABLE
     * @return result   of deleting that CID
     */
    public int deleteCell(int cellId) {
        Log.i(TAG, mTAG + "Deleted CID: " + cellId);
        // TODO Instead we need to delete this cell from DBi_measure, since:
        // we are using foreign_key enforced DB, that doesn't allow you to
        // remove Dbi_bts without corresponding DBi_measures that uses them.
        // Rewrite this query!
        return mDb.delete("DBi_bts", "CID=" + cellId, null);
    }

    /* ====================================================================
     *      mDb.query statements (get)        SELECT
     * ====================================================================
     *  IMPORTANT!  The SQL queries as presented here are not in the same order
     *              as in the DB tables themselves, therefore it may be hard
     *              to easily match the various items when using the Cursors
     *              as shown below.
     *
     *              For example, in the opencellid (DBe_import) table, the items are ordered as:
     *                 Lat,Lng,Mcc,Mnc,CellID,...
     *              whereas in the getOpenCellIDData() cursor, they are arranged as:
     *                 CellID,Lac,Mcc,Mnc,Lat,Lng,AvgSigStr,Samples
     *
     *              Thus when used in MapFragment.java at loadEntries() and
     *              loadOpenCellIDMarkers(), the index used there is completely different
     *              than what could be expected.
     *
     *  ISSUES:     [ ] To avoid un-necessary future code obscurity, we should rearrange
     *                  all Cursor queries to reflect the actual DB table order.
     *                  Todo: This is a tedious job...
     *                  TODO: Also AVOID using this type of Query!!
     *
     * ====================================================================
     */


    /**
     * Returns Cell Information (DBi_bts) database contents this returns BTSs that we logged and is
     * called from MapFragment.java to display cells on map<
     *
     * Because RAT is stored in measurement table. JOIN the two table together for a complete
     * picture.
     *
     * TODO: 2016-02-28 Consider if this is the best way to get the info. Database schema refactor?</p>
     *
     * @see #returnDBiBtsWithRAT()
     */
    public Cursor getCellData() {
        return returnDBiBtsWithRAT();
    }

    /**
     * Returns Cell Information for contribution to the OpenCellID project
     * by listing all rows from the DBi_measure table where isSubmitted is not 1.
     */
    public Cursor getOCIDSubmitData() {

        // This is used (prepareOpenCellUploadData) when uploading data to OCID
        // TODO: Use something like this instead... VVV Need testing may need ...,RAT FROM DBi_measure ..
        // @EVA created a new function getRatFromDBimeasure(CellId) to get RAT with CID
        //      this can be used when creating the upload data to add the RAT to the CSV file

        // IMPORTANT: Note the order of the items (to match CSV)!
        //TODO @EVA this is working but its returning 2 of each rows any ideas??
        String query = "SELECT DISTINCT MCC,MNC,LAC,CID,gpsd_lon,gpsd_lat,rx_signal,time,gpsd_accu FROM DBi_measure, DBi_bts WHERE isSubmitted <> 1 ORDER BY time;";
        //String query = "SELECT MCC,MNC,LAC,CID,gpsd_lon,gpsd_lat,rx_signal,time,gpsd_accu FROM DBi_bts WHERE isSubmitted <> 1"; // OLD query for reference
        return mDb.rawQuery(query, null);
    }


    // ====================================================================
    //      Various DB operations
    // ====================================================================

    /**
     *  Description:    This take a "Cell" bundle (from API) as input and uses its CID to check
     *                  in the DBi_measure (?) if there is already an associated LAC. It then
     *                  compares the API LAC to that of the DBi_Measure LAC.
     *
     *  Issues:     [ ] We should make all detections outside of AIMSICDDbAdapter.java in a
     *                  separate module as described in the diagram in GH issue #215.
     *                  https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/215
     *                  where it is referred to as "Detection Module" (DET)...
     *
     *              [ ] Seem we're querying too much, when we only need items: 1,3,4,8,11
     *                  (Try to avoid over query to improve performance.)
     *
     *              [ ]  V V V V
     *
     *              This is using the LAC found by API and comparing to LAC found from a previous
     *              measurement in the "DBi_measure". This is NOT depending on "DBe_import".
     *              This works for now...but we probably should consider populating "DBi_measure"
     *              as soon as the API gets a new LAC. Then the detection can be done by SQL,
     *              and by just comparing last 2 LAC entries for same CID.
     *
     * @return false if LAC is not OK (Cell's LAC differs from Cell's LAC previously stored value in DB)
     */
    public boolean checkLAC(Cell cell) {
        String query = String.format(Locale.US, "SELECT * FROM DBi_bts WHERE CID = %d", cell.getCID());  //CID

        Cursor bts_cursor = mDb.rawQuery(query, null);

        while (bts_cursor.moveToNext()) {
            // 1=LAC, 8=Accuracy, 11=Time
            if (cell.getLAC() != bts_cursor.getInt(bts_cursor.getColumnIndex("LAC"))) {
                Log.i(TAG, mTAG + "ALERT: Changing LAC on CID: " + cell.getCID()
                        + " LAC(API): " + cell.getLAC()
                        + " LAC(DBi): " + bts_cursor.getInt(bts_cursor.getColumnIndex("LAC")));

                bts_cursor.close();
                return false;
            } else {
                Log.i(TAG, mTAG + "LAC checked - no change on CID:" + cell.getCID()
                        + " LAC(API): " + cell.getLAC()
                        + " LAC(DBi): " + bts_cursor.getInt(bts_cursor.getColumnIndex("LAC")));
            }
        }
        bts_cursor.close();
        return true;
    }


    /**
     * UPDATE DBi_measure to indicate if OpenCellID DB contribution has been made
     */
    public void ocidProcessed() {
        ContentValues ocidValues = new ContentValues();
        ocidValues.put("isSubmitted", 1); // isSubmitted
        mDb.update("DBi_measure", ocidValues, "isSubmitted<>?", new String[]{"1"}); // isSubmitted
    }


    /**
     * This returns all BTS in the DBe_import by current sim card network rather
     * than returning other bts from different networks and slowing down map view
     */
    public Cursor returnOcidBtsByNetwork(int mcc, int mnc) {
        String query = String.format(Locale.US,
                "SELECT * FROM DBe_import WHERE MCC = %d AND MNC = %d", mcc, mnc);
        return mDb.rawQuery(query, null);
    }

    public double[] getDefaultLocation(int mcc) {
        String query = String.format(Locale.US,
                "SELECT lat, lon FROM defaultlocation WHERE MCC = %d", mcc);

        double[] loc = new double[2];
        Cursor cursor = mDb.rawQuery(query, null);

        if (cursor != null && cursor.moveToFirst()) {
            loc[0] = Double.parseDouble(cursor.getString(cursor.getColumnIndex("lat")));
            loc[1] = Double.parseDouble(cursor.getString(cursor.getColumnIndex("lon")));
        } else {
            loc[0] = 0.0;
            loc[1] = 0.0;
        }
        if (cursor != null) {
            cursor.close();
        }
        return loc;
    }

    /**
     * Description:     Remove all but the last row, unless its CID is invalid...
     *
     * Dependencies:    CellTracker.java:  ( dbHelper.cleanseCellTable(); )
     *
     * Issues:          [ ] This will not work if: PRAGMA foreign_key=ON, then we need to delete
     *                      the corresponding DBi_measure entries before / as well.
     *
     *                  [ ] TODO: It is UNCLEAR why this is needed!! It's probably an artifact of old DB tables??
     *                      TODO: Consider changing or removing!
     *
     * Notes:           Do we need to clean LAC as well? (Test with airplane-mode or roaming)
     *                  - probably not since a APM would give both LAC and CID as "-1".
     *
     */
    public void cleanseCellTable() {
        // This removes all but the last row in the "DBi_bts" table
        //"DELETE FROM DBi_bts WHERE _id NOT IN (SELECT MAX(_id) FROM DBi_bts) GROUP BY CID"
        mDb.execSQL("DELETE FROM DBi_bts WHERE _id NOT IN (SELECT MAX(_id) FROM DBi_bts)");

        // TODO: MOVE this, as this is only executed once!!
        // This removes erroneous BTS entries due to API giving you CID/LAC of "-1" or MAX_INT,
        // when either roaming, in airplane mode or during crappy hand-overs.
        String query2 = String.format(Locale.US, "DELETE FROM DBi_bts WHERE CID = %d OR CID = -1",
                Integer.MAX_VALUE);
        mDb.execSQL(query2);
    }

    /**
     * Description:     Prepares the CSV file used to upload new data to the OCID server.
     *
     * Issues:          TODO:
     *                  [ ] Add "act" in upload data for the DBi_measure:RAT
     *                  [ ] function getOCIDSubmitData() is not fully working ==> DB join not yet implemented
     *                  [ ] skip (or change) progress bar, since CSV write is too fast to be seen.
     *
     * Note:            Q: Where is this file?
     *                  A: It is wherever your device has mounted its SDCard.
     *                     For example, in:  /data/media/0/AIMSICD/OpenCellID
     *
     *                 OCID CSV upload format:
     *                  "cellid"        = CID (in UMTS long format)
     *                  "measured_at"   = time
     *                  "rating"        = gpsd_accu
     *                  "act"           = RAT (TEXT):
     *                                     1xRTT, CDMA, eHRPD, IS95A, IS95B, EVDO_0, EVDO_A, EVDO_B,
     *                                     UMTS, HSPA+, HSDPA, HSUPA, HSPA, LTE, EDGE, GPRS, GSM
     *
     */
    public boolean prepareOpenCellUploadData() {
        boolean result;

        File dir = new File(mExternalFilesDirPath + "OpenCellID/");
        if (!dir.exists()) {
            result = dir.mkdirs();
            if (!result) {
                return false;
            }
        }
        File file = new File(dir, "aimsicd-ocid-data.csv");

        try {
            // Get data not yet submitted:
            Cursor c = getOCIDSubmitData();
            // Check if we have something to upload:
            if (c.getCount() > 0) {
                if (!file.exists()) {
                    result = file.createNewFile();
                    if (!result) {
                        c.close();
                        return false;
                    }

                    // OCID CSV upload format and items
                    // mcc,mnc,lac,cellid,lon,lat,signal,measured_at,rating,speed,direction,act,ta,psc,tac,pci,sid,nid,bid
                    CSVWriter csvWrite = new CSVWriter(new FileWriter(file));
                    // TODO: Add "act"
                    csvWrite.writeNext("mcc,mnc,lac,cellid,lon,lat,signal,measured_at,rating");

                    int size = c.getCount();
                   Log.d(TAG, mTAG + "OCID UPLOAD: row count = " + size);

                    while (c.moveToNext()) {
                        csvWrite.writeNext(
                                String.valueOf(c.getInt(c.getColumnIndex("MCC"))),
                                String.valueOf(c.getInt(c.getColumnIndex("MNC"))),
                                String.valueOf(c.getInt(c.getColumnIndex("LAC"))),
                                String.valueOf(c.getInt(c.getColumnIndex("CID"))),
                                c.getString(c.getColumnIndex("gpsd_lon")),
                                c.getString(c.getColumnIndex("gpsd_lat")),
                                c.getString(c.getColumnIndex("rx_signal")),
                                c.getString(c.getColumnIndex("time")),
                                //c.getString(c.getColumnIndex("RAT")),                     // OCID: "act" TODO
                                String.valueOf(c.getInt(c.getColumnIndex("gpsd_accu"))));
                    }
                    csvWrite.close();
                    c.close();
                }
                return true;
            }
            c.close();
            return false;
        } catch (Exception e) {
           Log.e(TAG, mTAG + "prepareOpenCellUploadData(): Error creating OpenCellID Upload Data: ", e);
            return false;
        }
    }


    /**
     *  Description:    Parses the downloaded CSV from OpenCellID and uses it to populate
     *                  "DBe_import" table.
     *
     *
     *  Dependency:     RequestTask.java :: onPostExecute()
     *                  insertDBeImport()
     *  Issues:
     *
     *          [ ]     Progress bar is not shown or is the operation too quick to be seen?
     *          [ ]     Why are we only populating 8 items out of 19?
     *                  From downloaded OCID CSV file:  (19 items)
     *
     *  NOTES:
     *
     *          a)  We do not include "rej_cause" in backups. set to 0 as default
     *          b)  Unfortunately there are 2 important missing items in the OCID CSV file:
     *                  - "time_first"
     *                  - "time_last"
     *          c)  In addition the OCID data often contain unexplained negative values for one or both of:
     *                  - "samples"
     *                  - "range"
     *
     *          d) The difference between "Cellid" and "cid", is that "cellid" is the "Long CID",
     *             consisting of RNC and a multiplier:
     *                      Long CID = 65536 * RNC + CID
     *             See FAQ.
     *
     * ========================================================================
     * For details on available OpenCellID API DB values, see:
     * http://wiki.opencellid.org/wiki/API
     * http://wiki.opencellid.org/wiki/FAQ#Long_CellID_vs._short_Cell_ID
     * ========================================================================
     *   # head -2 opencellid.csv
     *   lat,lon,mcc,mnc,lac,cellid,averageSignalStrength,range,samples,changeable,radio,rnc,cid,psc,tac,pci,sid,nid,bid
     *
     *   0 lat                      TEXT
     *   1 lon                      TEXT
     *   2 mcc                      INTEGER
     *   3 mnc                      INTEGER
     *   4 lac                      INTEGER
     *   5 cellid                   INTEGER     (Long CID) = 65536 * RNC + CID
     *   6 averageSignalStrength    INTEGER     (rx_power)
     *   7 range                    INTEGER     (accu)
     *   8 samples                  INTEGER
     *   9 changeable               INTEGER     (isGPSexact)
     *   10 radio                   TEXT        (RAT)
     *   11 rnc                     INTEGER
     *   12 cid                     INTEGER     CID (Short)= "Long CID" mod 65536
     *   13 psc                     INTEGER
     *   --------- vvv  See OCID API  vvv ---------
     *   14 tac                     -
     *   15 pci                     -
     *   16 sid                     -
     *   17 nid                     -
     *   18 bid                     -
     *
     *   54.63376,25.160243,246,3,20,1294,0,-1,1,1,GSM,,,,,,,,
     * ========================================================================
     */
    public boolean populateDBeImport() {
        // This was not finding the file on a Samsung S5
        // String fileName = Environment.getExternalStorageDirectory()+ "/AIMSICD/OpenCellID/opencellid.csv";
        String fileName = mContext.getExternalFilesDir(null) + File.separator + "OpenCellID/opencellid.csv";
        File file = new File(fileName);

        try {
            if (file.exists()) {

                CSVReader csvReader = new CSVReader(new FileReader(file));
                List<String[]> csvCellID = new ArrayList<>();
                String next[];

                while ((next = csvReader.readNext()) != null) {
                    csvCellID.add(next);
                }

                if (!csvCellID.isEmpty()) {
                    int lines = csvCellID.size();
                    Log.i(TAG, mTAG + "UpdateOpenCellID: OCID CSV size (lines): " + lines);

                    // TODO: WHAT IS THIS DOING?? (Why is it needed?)
                    // This counts how many CIDs we have in DBe_import
                    Cursor lCursor = mDb.rawQuery("SELECT CID, COUNT(CID) FROM DBe_import GROUP BY CID", null);
                    SparseArray<Boolean> lPresentCellID = new SparseArray<>();
                    if (lCursor.getCount() > 0) {
                        while (lCursor.moveToNext()) {
                            lPresentCellID.put(lCursor.getInt(0), true);
                        }
                    }
                    lCursor.close();


                    int rowCounter;
                    for (rowCounter = 1; rowCounter < lines; rowCounter++) {
                        // TODO: IS this needed!???
                        // Inserted into the table only unique values CID
                        // without opening additional redundant cursor before each insert.
                        if (lPresentCellID.get(Integer.parseInt(csvCellID.get(rowCounter)[5]), false)) {
                            continue;
                        }
                        // Insert details into OpenCellID Database using:  insertDBeImport()
                        // Beware of negative values of "range" and "samples"!!
                        String lat = csvCellID.get(rowCounter)[0],          //TEXT
                                lon = csvCellID.get(rowCounter)[1],          //TEXT
                                mcc = csvCellID.get(rowCounter)[2],          //int
                                mnc = csvCellID.get(rowCounter)[3],          //int
                                lac = csvCellID.get(rowCounter)[4],          //int
                                cellid = csvCellID.get(rowCounter)[5],       //int   long CID [>65535]
                                range = csvCellID.get(rowCounter)[6],        //int
                                avg_sig = csvCellID.get(rowCounter)[7],      //int
                                samples = csvCellID.get(rowCounter)[8],      //int
                                change = csvCellID.get(rowCounter)[9],       //int
                                radio = csvCellID.get(rowCounter)[10],       //TEXT
//                                rnc = csvCellID.get(rowCounter)[11],         //int
//                                cid = csvCellID.get(rowCounter)[12],         //int   short CID [<65536]
                                psc = csvCellID.get(rowCounter)[13];         //int

                        // Some OCID data may not contain PSC so we indicate this with an out-of-range PSC value (666) > 512?.
                        // Should be -1 but hey people already imported so we're stuck with this...
                        int iPsc = 666;
                        if (psc != null && !psc.isEmpty()) {
                            iPsc = Integer.parseInt(psc);
                        }

                        //Reverse order 1 = 0 & 0 = 1
                        // what if ichange is 4? ~ agilob
                        int ichange = Integer.parseInt(change);
                        ichange = (ichange == 0 ? 1 : 0);

                        insertDBeImport(
                                "OCID",                     // DBsource
                                radio,                      // RAT
                                Integer.parseInt(mcc),      // MCC
                                Integer.parseInt(mnc),      // MNC
                                Integer.parseInt(lac),      // LAC
                                Integer.parseInt(cellid),   // CID (cellid) ?
                                iPsc,                       // psc
                                lat,                        // gps_lat
                                lon,                        // gps_lon
                                ichange,                    // isGPSexact
                                Integer.parseInt(avg_sig),  // avg_signal [dBm]
                                Integer.parseInt(range),    // avg_range [m]
                                Integer.parseInt(samples),  // samples
                                "n/a",                      // time_first  (not in OCID)
                                "n/a",                      // time_last   (not in OCID)
                                0                           // TODO: rej_cause , set default 0
                        );
                    }
                   Log.d(TAG, mTAG + "PopulateDBeImport(): inserted " + rowCounter + " cells.");
                }
            } else {
               Log.e(TAG, mTAG + "Opencellid.csv file does not exist!");
            }
            return true;
        } catch (Exception e) {
           Log.e(TAG, mTAG + "Error parsing OpenCellID data: " + e.getMessage());
            return false;
        } finally {
            try {
                Thread.sleep(1000); // wait 1 second to allow user to see progress bar.
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    //=============================================================================================
    // Database Backup and Restore
    //=============================================================================================

    /**
     *  Description:    Restores the database tables from a previously Exported CSV files.
     *                  One CSV file per table with the name:  "aimsicd-<table_name>.csv"
     *
     *  Issues:         [ ]
     *
     *  Notes:      1) Restoring the DB can be done from a monolithic SQLite3 DB by (check!):
     *                  # sqlite3 aimsicd.db <aimsicd.dump
     */
    public boolean restoreDB() {
        try {

            for (String table : mTables) {

                File file = new File(mExternalFilesDirPath + "aimsicd-" + table + ".csv");
                if (file.exists()) {
                    List<String[]> records = new ArrayList<>();
                    String next[];
                    CSVReader csvReader = new CSVReader(new FileReader(file));
                    while ((next = csvReader.readNext()) != null) {
                        records.add(next);
                    }

                    if (!records.isEmpty()) {
                        int lines = records.size();
                        for (int i = 1; i < lines; i++) {

                            switch (table) {
                                case "defaultlocation":
                                    try {
                                        insertDefaultLocation(
                                                records.get(i)[1],       // country
                                                Integer.parseInt(records.get(i)[2]), // MCC
                                                records.get(i)[3],       // lat
                                                records.get(i)[4]        // lon
                                        );
                                    } catch (Exception ee) {
                                       Log.e(TAG, mTAG + "RestoreDB: Error in insertDefaultLocation()", ee);
                                    }
                                    break;

                                case "API_keys":
                                    insertApiKeys(
                                            records.get(i)[1],           //name
                                            records.get(i)[2],           //type
                                            records.get(i)[3],           //key
                                            records.get(i)[4],           //time_add
                                            records.get(i)[5]            //time_exp
                                    );
                                    break;

                                case "CounterMeasures":
                                    insertCounterMeasures(
                                            records.get(i)[1],                      //name
                                            records.get(i)[2],                      //description
                                            Integer.parseInt(records.get(i)[3]),    //thresh
                                            Double.parseDouble(records.get(i)[4])   //thfine
                                    );
                                    break;

                                case "DBe_capabilities":
                                    insertDBeCapabilities(
                                            records.get(i)[1],           //MCC
                                            records.get(i)[2],           //MNC
                                            records.get(i)[3],           //LAC
                                            records.get(i)[4],           //op_name
                                            records.get(i)[5],           //band_plan
                                            records.get(i)[6]            //__EXPAND___
                                    );

                                    break;

                                case "DBe_import":
                                    try {
                                        insertDBeImport(
                                                records.get(i)[1],                      // DBsource
                                                records.get(i)[2],                      // RAT
                                                Integer.parseInt(records.get(i)[3]),    // MCC
                                                Integer.parseInt(records.get(i)[4]),    // MNC
                                                Integer.parseInt(records.get(i)[5]),    // LAC
                                                Integer.parseInt(records.get(i)[6]),    // CID
                                                Integer.parseInt(records.get(i)[7]),    // PSC ??
                                                records.get(i)[8],                      // gps_lat
                                                records.get(i)[9],                      // gps_lon
                                                Integer.parseInt(records.get(i)[10]),   // isGPSexact
                                                Integer.parseInt(records.get(i)[11]),   // avg_range
                                                Integer.parseInt(records.get(i)[12]),   // avg_signal
                                                Integer.parseInt(records.get(i)[13]),   // samples
                                                records.get(i)[14],                     // time_first
                                                records.get(i)[15],                     // time_last
                                                0 //Integer.parseInt(records.get(i)[16])  // TODO: rej_cause
                                        );
                                    } catch (Exception ee) {
                                       Log.e(TAG, mTAG + "RestoreDB: Error in insertDBeImport()", ee);
                                    }
                                    break;

                                case "DBi_bts":
                                    try {
                                        insertBTS(
                                                Integer.parseInt(records.get(i)[1]),    // MCC
                                                Integer.parseInt(records.get(i)[2]),    // MNC
                                                Integer.parseInt(records.get(i)[3]),    // LAC
                                                Integer.parseInt(records.get(i)[4]),    // CID
                                                Integer.parseInt(records.get(i)[5]),    // PSC
                                                Integer.parseInt(records.get(i)[6]),    // T3212
                                                Integer.parseInt(records.get(i)[7]),    // A5x
                                                Integer.parseInt(records.get(i)[8]),    // ST_id
                                                records.get(i)[9],                      // time_first
                                                records.get(i)[10],                     // time_last
                                                Double.parseDouble(records.get(i)[11]), // lat
                                                Double.parseDouble(records.get(i)[12])  // lon
                                        );
                                    } catch (Exception ee) {
                                       Log.e(TAG, mTAG + "RestoreDB: Error in insertBTS()", ee);
                                    }
                                    break;

                                case "DBi_measure":
                                    try {
                                        insertDbiMeasure(
                                                Integer.parseInt(records.get(i)[1]),    // bts_id
                                                records.get(i)[2],                      // nc_list
                                                records.get(i)[3],                      // time
                                                records.get(i)[4],                      // gpsd_lat
                                                records.get(i)[5],                      // gpsd_lon
                                                Integer.parseInt(records.get(i)[6]),    // gpsd_accu
                                                records.get(i)[7],                      // gpse_lat TODO: remove!
                                                records.get(i)[8],                      // gpse_lon TODO: remove!
                                                records.get(i)[9],                      // bb_power
                                                records.get(i)[10],                     // bb_rf_temp
                                                records.get(i)[11],                     // tx_power
                                                records.get(i)[12],                     // rx_signal
                                                records.get(i)[13],                     // rx_stype
                                                records.get(i)[14],                     // RAT
                                                records.get(i)[15],                     // BCCH
                                                records.get(i)[16],                     // TMSI
                                                Integer.parseInt(records.get(i)[17]),   // TA
                                                Integer.parseInt(records.get(i)[18]),   // PD
                                                Integer.parseInt(records.get(i)[19]),   // BER
                                                records.get(i)[20],                     // AvgEcNo
                                                Integer.parseInt(records.get(i)[21]),   // isSubmitted
                                                Integer.parseInt(records.get(i)[22])    // isNeighbour
                                                //records.get(i)[23].toString()         // TODO: con_state
                                        );
                                    } catch (Exception ee) {
                                       Log.e(TAG, mTAG + "RestoreDB: Error in insertDbiMeasure()", ee);
                                    }
                                    break;

                                case "DetectionFlags":
                                    insertDetectionFlags(
                                            Integer.parseInt(records.get(i)[1]),    //code
                                            records.get(i)[2],                      //name
                                            records.get(i)[3],                      //description
                                            Integer.parseInt(records.get(i)[4]),    //p1
                                            Integer.parseInt(records.get(i)[5]),    //p2
                                            Integer.parseInt(records.get(i)[6]),    //p3
                                            Double.parseDouble(records.get(i)[7]),  //p1_fine
                                            Double.parseDouble(records.get(i)[8]),  //p2_fine
                                            Double.parseDouble(records.get(i)[9]),  //p3_fine
                                            records.get(i)[10],                     //app_text
                                            records.get(i)[11],                     //func_use
                                            Integer.parseInt(records.get(i)[12]),   //istatus
                                            Integer.parseInt(records.get(i)[13])    //CM_id

                                    );
                                    break;

                                case "EventLog":
                                    insertEventLog(
                                            records.get(i)[1],                      //time
                                            Integer.parseInt(records.get(i)[2]),    //LAC
                                            Integer.parseInt(records.get(i)[3]),    //CID
                                            Integer.parseInt(records.get(i)[4]),    //PSC
                                            records.get(i)[5],                      //gpsd_lat
                                            records.get(i)[6],                      //gpsd_lon
                                            Integer.parseInt(records.get(i)[7]),    //gpsd_accu
                                            Integer.parseInt(records.get(i)[8]),    //DF_id
                                            records.get(i)[9]                       //DF_desc
                                    );
                                    break;

                                case "SectorType":
                                    insertSectorType(records.get(i)[1]);
                                    break;

                                case "DetectionStrings":
                                    insertDetectionStrings(
                                            records.get(i)[1],
                                            records.get(i)[2]);
                                    break;

                                case "SmsData":
                                    insertSmsData(
                                            records.get(i)[1],                      //time
                                            records.get(i)[2],                      //number
                                            records.get(i)[3],                      //smsc
                                            records.get(i)[4],                      //message
                                            records.get(i)[5],                      //type
                                            records.get(i)[6],                      //class
                                            Integer.parseInt(records.get(i)[7]),    //lac
                                            Integer.parseInt(records.get(i)[8]),    //cid
                                            records.get(i)[9],                      //rat
                                            Double.parseDouble(records.get(i)[10]), //gps_lat
                                            Double.parseDouble(records.get(i)[11]), //gps_lon
                                            Integer.parseInt(records.get(i)[12])    //isRoaming
                                    );
                                    break;
                            }
                        }
                    }
                }
            }
            Log.i(TAG, mTAG + "RestoreDB() Finished");
            return true;
        } catch (Exception e) {
           Log.e(TAG, mTAG + "RestoreDB() Error", e);
            return false;
        }

    }

    /**
     *  Description:    Dumps the internal aimsicd.db to a file called "aimsicd_dump.db".
     *
     *  Requires:       root + SQLite3 binary
     *
     *  Where?          Used in backupDB() and depend on the  MONO_DB_DUMP  boolean.
     *
     *  Notes:  1) We probably also need to test if we have the sqlite3 binary. (See Busybox checking code.)
     *
     *          2) Apparently pipes doesn't work from Java... No idea why, as they appear to work
     *              in the AtCommandFragment.java... for checking for /dev/ files.
     *
     *          3) We can use either ".dump" or ".backup", but "dump" makes an SQL file,
     *             whereas "backup" make a binary SQLite DB.
     *
     *            a) # sqlite3 aimsicd.db '.dump' | gzip -c >aimsicd.dump.gz
     *            b) # sqlite3 aimsicd.db '.dump' >aimsicd.dump
     *            c) # sqlite3 aimsicd.db '.backup aimsicd.back'
     *
     *    execString = "/system/xbin/sqlite3 " + dir + "aimsicd.db '.dump' | gzip -c >" + file;
     *    execString = "/system/xbin/sqlite3 " + aimdir + "aimsicd.db '.dump' >" + file;
     *    execString = "/system/xbin/sqlite3 " + aimdir + "aimsicd.db '.backup " +file + "'";
     *
     *          4) To re-import use:
     *             # zcat aimsicd.dump.gz | sqlite3 aimsicd.db
     *
     */
    private void dumpDB() {

        File dumpdir = new File(mExternalFilesDirPath);
        //if (!dir.exists()) { dir.mkdirs(); }
        File file = new File(dumpdir, "aimsicd_dump.db");
        //Bad coding? (What is AOS standard?)
        //Context.getFilesDir().getPath("com.SecUpwN.AIMSICD/databases"); ????
        String aimdir = new File(mDatabasePath).getParent();
        String execString = "/system/xbin/sqlite3 " + aimdir + "aimsicd.db '.backup " + file + "'";

        try {
            Log.i(TAG, mTAG + "DumpDB() Attempting to dump DB to: " + file + "\nUsing: \"" + execString + "\"\n");
            CMDProcessor.runSuCommand(execString); // We need SU for this...
        } catch (Exception e) {
           Log.e(TAG, mTAG + "DumpDB() Failed to export DB dump file: ", e);
        }
        Log.i(TAG, mTAG + "DumpDB() Dumped internal database to: " + aimdir + file);
    }


    /**
     *  Description:    Backup the database tables to CSV files (or monolithic dump file)
     *
     *  Depends:        On the Boolean MONO_DB_DUMP to indicate if we want to try to
     *                  dump a monolithic DB using the rooted shell + sqlite3 binary
     *                  method above.
     *
     * @return boolean indicating backup outcome
     */
    public boolean backupDB() {
        try {
            for (String table : mTables) {
                backup(table);
            }
            if (MONO_DB_DUMP) {
                dumpDB();
            }
            return true;
        } catch (Exception ioe) {
           Log.e(TAG, mTAG + "BackupDB() Error: ", ioe);
            return false;
        }
    }

    /**
     * Exports the database tables to CSV files
     *
     * @param tableName String representing table name to export
     */
    private void backup(String tableName) {
        Log.i(TAG, mTAG + "Database Backup: " + DB_NAME);

        File dir = new File(mExternalFilesDirPath);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
               Log.e(TAG, mTAG + "Backup(): Cannot create directory structure to " + dir.getAbsolutePath());
            }
        }  // We should probably add some more error handling here.
        File file = new File(dir, "aimsicd-" + tableName + ".csv");

        try {
            Log.i(TAG, mTAG + "Backup(): Backup file was created? " + file.createNewFile());
            CSVWriter csvWrite = new CSVWriter(new FileWriter(file));
           Log.d(TAG, mTAG + "DB backup() tableName: " + tableName);

            Cursor c = mDb.rawQuery("SELECT * FROM " + tableName, new String[0]);

            csvWrite.writeNext(c.getColumnNames());
            String[] rowData = new String[c.getColumnCount()];
            int size = c.getColumnCount();

            while (c.moveToNext()) {
                for (int i = 0; i < size; i++) {
                    rowData[i] = c.getString(i);
                }
                csvWrite.writeNext(rowData);
            }
            csvWrite.close();
            c.close();

        } catch (Exception e) {
           Log.e(TAG, mTAG + "Error exporting table: " + tableName, e);
        }
        Log.i(TAG, mTAG + "Backup(): Successfully exported DB table to: " + file);
    }


    // ====================================================================
    //      Cleanup and filtering of DB tables
    // ====================================================================

    /**
     *  What:           This is the DBe_import data consistency check
     *
     *  Description:    This method checks each imported BTS data for consistency
     *                  and correctness according to general 3GPP LAC/CID/RAT rules
     *                  and according to the app settings:
     *
     *                  tf_settings         (currently hard-coded)
     *                  min_gps_precision   (currently hard-coded)
     *
     *                  So there are really two steps in this procedure:
     *                  a) Remove bad BTSs from DBe_import
     *                  b) Mark unsafe BTSs in the DBe_import with "rej_cause" value.
     *
     *                  See:    #253    http://tinyurl.com/lybrfxb
     *                          #203    http://tinyurl.com/mzgjdcz
     *
     *                  We filter:
     *
     *  Used:
     *                  RequestTask.java :: onPostExecute()
     *
     *  Issues:
     *
     *          [ ] Look into "long CID" and "Short CID" for UMTS/LTE...
     *              http://wiki.opencellid.org/wiki/FAQ
     *
     *              The formula for the long cell ID is as follows:
     *                  Long CID = 65536 * RNC + CID
     *
     *              If you have the Long CID, you can get RNC and CID in the following way:
     *                  RNC = Long CID / 65536 (integer division)
     *                  CID = Long CID mod 65536 (modulo operation)
     *
     *
     *  2017-04-21  E:V:A   These VVVV are probably outdated. Trying to repair this file!!
     *
     *  TODO:   (1) Implement some kind of counter, to count how many cells was removed.
     *  TODO:   (2) Better description of what was removed.
     *  TODO:   (3) Give a return value for success/failure
     *  TODO:   (4) Implement the "rej_cause" check and UPDATE table.
     *
     * Notes:   (a) By using rawQuery, we could count the number of items affected.
     *                  mDb.rawQuery(sqlq, null);
     *              But rawQuery() is not executed until there is an associated Cursor operation!
     *
     *          (b)
     *
     *
     *  ChangeLog:
     *          2015-08-01  E:V:A           Updated Queries to reflect new DB structure
     *
     */
    public void checkDBe() {
        // We hard-code these for now, but should be in the settings eventually
//        int tf_settings = 30;         // [days] Minimum acceptable number of days since "time_first" seen.
        int min_gps_precision = 50;   // [m]    Minimum acceptable GPS accuracy in meters.

        String sqlQuery;                // SQL Query string

        //=============================================================
        //===  DELETE bad cells from BTS data
        //=============================================================

       Log.d(TAG, mTAG + "CheckDBe() Attempting to delete bad import data from DBe_import table...");

        // =========== samples ===========
        sqlQuery = "DELETE FROM DBe_import WHERE samples < 1";
        mDb.execSQL(sqlQuery);

        // =========== avg_range ===========
        // TODO: OCID data marks many good BTS with a negative range so we can't use this yet.
        // TODO: Also delete cells where the avg_range is way too large, say > 2000 meter
        //sqlQuery = "DELETE FROM DBe_import WHERE avg_range < 1 OR avg_range > 2000";
        //mDb.rawQuery(sqlQuery, null);

        // =========== LAC ===========
        sqlQuery = "DELETE FROM DBe_import WHERE LAC < 1";
        mDb.execSQL(sqlQuery);

        // We should delete cells with CDMA (4) LAC not in [1,65534] but we can simplify this to:
        // Delete ANY cells with a LAC not in [1,65534]
        sqlQuery = "DELETE FROM DBe_import WHERE LAC > 65534";
        mDb.execSQL(sqlQuery);

        // Delete cells with GSM/UMTS/LTE (1/2/3/13 ??) (or all others?) LAC not in [1,65533]
        //sqlQuery = "DELETE FROM DBe_import WHERE LAC > 65533 AND RAT != 'CDMA'";
        //mDb.rawQuery(sqlQuery, null);

        // =========== CID ===========
        sqlQuery = "DELETE FROM DBe_import WHERE CID < 1";
        mDb.execSQL(sqlQuery);

        // We should delete cells with UMTS/LTE (3,13) CID not in [1,268435455] (0xFFF FFFF) but
        // we can simplify this to:
        // Delete ANY cells with a CID not in [1,268435455]
        sqlQuery = "DELETE FROM DBe_import WHERE CID > 268435455";
        mDb.execSQL(sqlQuery);

        // Delete cells with GSM/CDMA (1-3,4) CID not in [1,65534]
        sqlQuery = "DELETE FROM DBe_import WHERE CID > 65534 AND (RAT='GSM' OR RAT='CDMA')";
        mDb.execSQL(sqlQuery);

        // SELECT count(*) from DBe_import;
        Log.i(TAG, mTAG + "CheckDBe() Deleted BTS entries from DBe_import table with bad LAC/CID...");

        //=============================================================
        //===  UPDATE "rej_cause" in BTS data (DBe_import)
        //=============================================================

        // =========== isGPSexact ===========
        // Increase rej_cause, when:  the GPS position of the BTS is not exact:
        // NOTE:  In OCID: "changeable"=1 ==> isGPSexact=0
        sqlQuery = "UPDATE DBe_import SET rej_cause = rej_cause + 3 WHERE isGPSexact=0";
        mDb.execSQL(sqlQuery);

        // =========== avg_range ===========
        // Increase rej_cause, when:  the average range is < a minimum GPS precision
        sqlQuery = "UPDATE DBe_import SET rej_cause = rej_cause + 3 WHERE avg_range < " + min_gps_precision;
        mDb.execSQL(sqlQuery);

        // =========== time_first ===========
        // Increase rej_cause, when:  the time first seen is less than a number of days.
        // TODO: We need to convert tf_settings to seconds since epoch/unix time...
        //      int tf_settings = current_time[s] - (3600 * 24 * tf_settings) ???
        //sqlQuery = "UPDATE DBe_import SET rej_cause = rej_cause + 1 WHERE time_first < " + tf_settings;
        //mDb.execSQL(sqlQuery);

    }


    // =======================================================================================
    //      Signal Strengths Table
    // =======================================================================================

    /**
     * Description:     Remove too old signal strengths entries from DBi_measure table,
     *                  given a particular LAC,CID,PSC,RAT (or all?).
     *
     *                  TODO: Why do we need this at all?
     *
     * Note:            WARNING!    Do not remove based upon time only, as that would remove
     *                              all other measurement entries as well.
     *
     * Issues:          TODO:   timestamp in DBi_measure is a String,
     *                          but the one from SignalStrengthTracker is a long
     */

    public void cleanseCellStrengthTables(long maxTime) {
       Log.d(TAG, mTAG + "CleanseCellStrengthTables(): Cleaning DBi_measure WHERE time < " + maxTime);

        //TODO Change "time" to INTEGER in DB   -- currently not working
        String query = String.format(Locale.US,
                "DELETE FROM DBi_measure WHERE time < %d",
                maxTime);
        mDb.execSQL(query);
    }

    // TODO: Where is this used?
    public int getAverageSignalStrength(int cellID) {
        String query = String.format(Locale.US,
                // todo: "SELECT AVG(rx_signal) FROM DBi_measure,DBi_bts WHERE CID= %d",  // E:V:A
                "SELECT avg(rx_signal) FROM DBi_measure WHERE bts_id= %d",
                cellID);
        Cursor c = mDb.rawQuery(query, null);
        c.moveToFirst();
        int lAnswer = c.getInt(0);
        c.close();
        return lAnswer;
    }

    //=============================================================================================
    // ********************** ALL NEW FUNCTIONS ADDED AFTER THIS LINE *****************************
    //=============================================================================================

    // ====================================================================
    //      Get all detection strings
    // ====================================================================
    public List<AdvanceUserItems> getDetectionStrings() {

        Cursor cursor = mDb.rawQuery("SELECT * FROM DetectionStrings", null);

        List<AdvanceUserItems> detectionStrings = new ArrayList<>();

        if (cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                AdvanceUserItems setItems = new AdvanceUserItems();
                setItems.setDetection_string(cursor.getString(cursor.getColumnIndex("det_str")));
                setItems.setDetection_type(cursor.getString(cursor.getColumnIndex("sms_type")));
                detectionStrings.add(setItems);

            }
        } else {
            AdvanceUserItems setItems = new AdvanceUserItems();
            setItems.setDetection_string("No data");
            setItems.setDetection_type("No data");
            detectionStrings.add(setItems);
        }
        cursor.close();
        return detectionStrings;
    }

    public boolean deleteDetectedSms(long deleteme) {
        String mTAG = "AIMSICDDbAdapter";

        try {
            mDb.delete("SmsData", "_id=" + deleteme, null);
            return true;
        } catch (Exception ee) {
            Log.i(TAG, mTAG + mTAG + ": Deleting SMS data failed", ee);
        }
        return false;
    }

    public boolean deleteDetectionString(String deleteme) {

        try {
            mDb.delete("DetectionStrings", "det_str='" + deleteme + "'", null);
            return true;
        } catch (Exception ee) {
            Log.i(TAG, mTAG + "Deleting detection string failed", ee);
        }
        return false;

    }

    //====================================================================
    //      Insert new detection strings into database
    //====================================================================

    /**
     * Description:     When inserting strings it has to be in the format:
     *                  "i am a type 0 string". These strings can be found in main logcat.
     *
     * Issues:          [ ] Need to change time data type to INTEGER in DB
     *
     * NOTES:
     *
     *      TYPE0 SILENTVOICE FLASH <--- These have to be in CAPS
     *      ContentValues newconvalues = new ContentValues();
     *      newconvalues.put(DETECTION_STRINGS_LOGCAT_STRING, "your string goes here");
     *      newconvalues.put(DETECTION_STRINGS_SMS_TYPE, "TYPE0");
     *      database.insert(DETECTION_STRINGS_TABLE_NAME,,null,newconvalues);
     *
     * ChangeLog:
     *
     */
    public boolean insertNewDetectionString(ContentValues newString) {

        // First check that string not in DB
        String check4String = String.format(Locale.US,
                "SELECT * FROM DetectionStrings WHERE det_str = \"%s\"",
                newString.get("det_str").toString());

        Cursor cursor = mDb.rawQuery(check4String, null);
        final boolean exists = cursor.getCount() > 0;
        cursor.close();

        if (exists) {
            Log.i(TAG, mTAG + "Detection String already in Database");
        } else {
            try {
                mDb.insert("DetectionStrings", null, newString);
                Log.i(TAG, mTAG + "New detection string added.");
                return true;
            } catch (Exception ee) {
                Log.i(TAG, mTAG + "Adding detection string Failed! ", ee);
            }
        }
        return false;
    }

    public CapturedSmsData storeCapturedSms(CapturedSmsData smsdata) {

        ContentValues values = new ContentValues();

        values.put("number", smsdata.getSenderNumber());
        values.put("message", smsdata.getSenderMsg());
        values.put("time", smsdata.getSmsTimestamp());
        values.put("type", smsdata.getSmsType());
        values.put("lac", smsdata.getCurrent_lac());
        values.put("cid", smsdata.getCurrent_cid());
        values.put("rat", smsdata.getCurrent_nettype());
        values.put("isRoaming", smsdata.getCurrent_roam_status());
        values.put("gps_lat", smsdata.getCurrent_gps_lat());
        values.put("gps_lon", smsdata.getCurrent_gps_lon());

        long insertId = mDb.insert("SmsData", null, values);
        smsdata.setId(insertId);
        return smsdata;
    }

    // Boolean Check if a give timestamp already exists in SmsData table
    public boolean isTimeStampInDB(String timestamp) {
        String check4timestamp = String.format(Locale.US,
                "SELECT time FROM SmsData WHERE time = \"%s\"", timestamp);
        Cursor cursor = mDb.rawQuery(check4timestamp, null);
        final boolean exists = cursor.getCount() > 0;
        cursor.close();
        return exists;
    }

    //====================================================================
    //      RETURN DATABASE CURSORS START HERE
    //
    // @banjaxbanjo:    (1) What do you mean with this actually?
    //                  (2) Where are these used?
    //                  (3) Are you making cursors?
    //
    // TODO:  Because of many changes to DB design, please keep these
    // TODO:  "Returned Columns" comments updated.
    //====================================================================

    /*
        Returned Columns:
        "_id"       INTEGER PRIMARY KEY,
        "country"   TEXT,
        "MCC"       INTEGER,
        "lat"       TEXT,
        "lon"       TEXT
        returns Default Mcc Locations
     */
    public Cursor returnDefaultLocation() {
        return mDb.rawQuery("SELECT * FROM defaultlocation", null);
    }

    /**
     * Returns DBe_import contents
     *
     * Used in:
     * DbViewerFragment.java
     * MapFragment.java
     *
     * Returned Columns:
     * "_id"        INTEGER PRIMARY KEY AUTOINCREMENT,
     * "DBsource"   TEXT NOT NULL,
     * "RAT"        TEXT,
     * "MCC"        INTEGER,
     * "MNC"        INTEGER,
     * "LAC"        INTEGER,
     * "CID"        INTEGER,
     * "PSC"        INTEGER,
     * "gps_lat"    REAL,
     * "gps_lon"    REAL,
     * "isGPSexact" INTEGER,
     * "avg_range"  INTEGER,
     * "avg_signal" INTEGER,
     * "samples"    INTEGER,
     * "time_first" INTEGER,
     * "time_last"  INTEGER,
     * "rej_cause"  INTEGER
     */
    public Cursor returnDBeImport() {
        return mDb.rawQuery("SELECT * FROM DBe_import", null);
    }

    // TODO: THESE ARE OUTDATED!! Please see design and update
    /*
    Returned Columns:
        "_id"           INTEGER PRIMARY KEY AUTOINCREMENT,
        "MCC"           INTEGER NOT NULL,
        "MNC"           INTEGER NOT NULL,
        "LAC"           INTEGER NOT NULL,
        "CID"           INTEGER NOT NULL,
        "PSC"           INTEGER,
        "T3212"         INTEGER,
        "A5x"           INTEGER,
        "ST_id"         INTEGER,
        "time_first"    INTEGER,
        "time_last"     INTEGER,
        "LAT"           REAL NOT NULL,  -- NULL should be okay, as GPS is not always available
        "LON"           REAL NOT NULL
    */
    public Cursor returnDBiBts() {
        return mDb.rawQuery("SELECT * FROM DBi_bts", null);
    }
    public Cursor returnDBiBtsWithRAT() {
        // Basically the same as above, but with RAT from a joined, related table
        return mDb.rawQuery("SELECT DBi_bts.*, DBi_measure.RAT FROM DBi_bts JOIN DBi_measure ON DBi_measure.bts_id = DBi_bts.CID", null);
    }

    // TODO: THESE ARE OUTDATED!! Please see design and update
    /*
    Returned Columns:
        "_id"           INTEGER PRIMARY KEY AUTOINCREMENT,
        "bts_id"        INTEGER NOT NULL,
        "nc_list"       TEXT,
        "time"          TEXT NOT NULL,
        "gpsd_lat"      TEXT NOT NULL,
        "gpsd_lon"      TEXT NOT NULL,
        "gpsd_accuracy" INTEGER,
        "gpse_lat"      TEXT,
        "gpse_lon"      TEXT,
        "bb_power"      TEXT,
        "bb_rf_temp"    TEXT,
        "tx_power"      TEXT,
        "rx_signal"     TEXT,
        "rx_stype"      TEXT,
        "RAT"           TEXT,
        "BCCH"          TEXT,
        "TMSI"          TEXT,
        "TA"            INTEGER,
        "PD"            INTEGER,
        "BER"           INTEGER,
        "AvgEcNo"       TEXT,
        "isSubmitted"   INTEGER DEFAULT 0,
        "isNeighbour"   INTEGER DEFAULT 0,
*/
    public Cursor returnDBiMeasure() {
        // This is special since this table is linked to DBi_bts by a foreign key,
        // so if you're not able to get LAC/CID, it's probably due to the FOREIGN KEY Pragma being OFF,
        // then try with:
        // "SELECT * FROM DBi_measure, DBi_bts WHERE DBi_measure.bts_id=DBi_bts.CID;"
        // NOTE:  TODO: bts_id should not be populated, then replace with "DBi_measure.bts_id=DBi_bts._id;"
        return mDb.rawQuery("SELECT * FROM DBi_measure", null);
    }

    /*
    Returned Columns
        "_id"           INTEGER PRIMARY KEY,
        "code"          INTEGER,
        "name"          TEXT,
        "description"   TEXT,
        "p1"            INTEGER,
        "p2"            INTEGER,
        "p3"            INTEGER,
        "p1_fine"       REAL,
        "p2_fine"       REAL,
        "p3_fine"       REAL,
        "app_text"      TEXT,
        "func_use"      TEXT,
        "istatus"       INTEGER,
        "CM_id"         INTEGER
     */
    public Cursor returnDetectionFlags() {
        return mDb.rawQuery("SELECT * FROM DetectionFlags", null);
    }

    // TODO: THESE ARE OUTDATED!! Please see design and update

    /**
     * Returned Columns:
     * "_id"            INTEGER PRIMARY KEY AUTOINCREMENT,
     * "time"           TEXT NOT NULL,
     * "LAC"            INTEGER NOT NULL,
     * "CID"            INTEGER NOT NULL,
     * "PSC"            INTEGER,
     * "gpsd_lat"       TEXT,--Should this be double?
     * "gpsd_lon"       TEXT,--Should this be double?
     * "gpsd_accu"      INTEGER,
     * "DF_id"          INTEGER,
     * "DF_description" TEXT,
     */
    public Cursor returnEventLogData() {
        return mDb.rawQuery("SELECT * FROM EventLog", null);
    }

    /**
     * Returned Columns:
     * "_id"            INTEGER PRIMARY KEY AUTOINCREMENT,
     * "detection_str"  TEXT,
     * "sms_type"       TEXT--(WapPush MWI TYPE0 etc..)
     */
    public Cursor returnDetectionStrings() {
        return mDb.rawQuery("SELECT * FROM DetectionStrings", null);
    }

    // TODO: THESE ARE OUTDATED!! Please see design and update
    /*
    Returned Columns:
    "_id"       INTEGER PRIMARY KEY AUTOINCREMENT,
    "time"      TEXT,
    "number"    TEXT,
    "smsc"      TEXT,
    "message"   TEXT,
    "type"      TEXT,   -- WapPush MWI TYPE0 etc..)
    "class"     TEXT,   --(CLASS 0 etc...)
    "lac"       INTEGER,
    "cid"       INTEGER,
    "rat"       TEXT,
    "gps_lat"   REAL,
    "gps_lon"   REAL,
    "isRoaming" INTEGER
 */
    public Cursor returnSmsData() {
        return mDb.rawQuery("SELECT * FROM SmsData", null);
    }

    //----END OF RETURN DATABASE CURSORS------//

    //====================================================================
    //      START OF INSERT FUNCTIONS
    //====================================================================

    // TODO: Remove this, it's confusing and doesn't seem necessary!
    public void insertDefaultLocation(String country, int mcc, String lat, String lon) {

        ContentValues def_location = new ContentValues();
        def_location.put("country", country);
        def_location.put("MCC", mcc);
        def_location.put("lat", lat);
        def_location.put("lon", lon);

        // Check that the country and MCC not known in the DefaultLocation DB to avoid duplicates
        // TODO: @banjaxbanjo:  Please clarify its use:
        //                      Why would there be duplicates?
        //                      All countries are pre-loaded??
        String query = String.format(Locale.US,
                "SELECT * FROM defaultlocation WHERE country = \"%s\" AND MCC = %d ",
                country, mcc);

        Cursor cursor = mDb.rawQuery(query, null);
        if (cursor.getCount() <= 0) {
            // <= 0 means country is not in database yet
            mDb.insert("defaultlocation", null, def_location);
        }
        cursor.close();
    }

    public boolean insertApiKeys(String name,
                                 String type,
                                 String key,
                                 String time_add,
                                 String time_exp) {

        ContentValues ApiKeys = new ContentValues();
        ApiKeys.put("name", name);
        ApiKeys.put("type", type);
        ApiKeys.put("key", key);
        ApiKeys.put("time_add", time_add);
        ApiKeys.put("time_exp", time_exp);

        String query = String.format(Locale.US, "SELECT * FROM API_keys WHERE key = \"%s\"", key);
        Cursor cursor = mDb.rawQuery(query, null);

        // Only insert a new key if the key not already in DB
        if (cursor.getCount() <= 0) {
            mDb.insert("API_keys", null, ApiKeys);
            cursor.close();
            return true;
        }
        cursor.close();
        return false;
    }

    public void insertCounterMeasures(String name, String description,
                                      int thresh, double thfine) {

        ContentValues counterMeasures = new ContentValues();
        counterMeasures.put("name", name);
        counterMeasures.put("description", description);
        counterMeasures.put("thresh", thresh);
        counterMeasures.put("thfine", thfine);

        mDb.insert("CounterMeasures", null, counterMeasures);
    }

    /**
     * "MCC"        TEXT,
     * "MNC"        TEXT,
     * "LAC"        TEXT,
     * "op_name"    TEXT,
     * "band_plan"  TEXT,
     * "__EXPAND__" TEXT
     */
    public void insertDBeCapabilities(String mcc, String mnc, String lac, String op_name,
                                      String bandPlan, String __EXPAND__) {

        ContentValues dbeCapabilities = new ContentValues();

        dbeCapabilities.put("MCC", mcc);
        dbeCapabilities.put("MNC", mnc);
        dbeCapabilities.put("LAC", lac);
        dbeCapabilities.put("op_name", op_name);
        dbeCapabilities.put("band_plan", bandPlan);
        dbeCapabilities.put("__EXPAND__", __EXPAND__);

        mDb.insert("DBe_capabilities", null, dbeCapabilities);
    }

    /**
     * This method is used to insert and populate the downloaded or previously
     * backed up OCID details into the DBe_import database table.
     *
     * It also prevents adding multiple entries of the same cell-id, when OCID
     * downloads are repeated.
     */
    public void insertDBeImport(String db_src, String rat, int mcc, int mnc, int lac,
                                int cid, int psc, String lat, String lon, int isGpsExact,
                                int avg_range, int avg_signal, int samples, String time_first,
                                String time_last, int rej_cause) {


        ContentValues dbeImport = new ContentValues();

        dbeImport.put("DBsource", db_src);
        dbeImport.put("RAT", rat);
        dbeImport.put("MCC", mcc);
        dbeImport.put("MNC", mnc);
        dbeImport.put("LAC", lac);
        dbeImport.put("CID", cid);
        dbeImport.put("PSC", psc);
        dbeImport.put("gps_lat", lat);
        dbeImport.put("gps_lon", lon);
        dbeImport.put("isGPSexact", isGpsExact);
        dbeImport.put("avg_range", avg_range);
        dbeImport.put("avg_signal", avg_signal);
        dbeImport.put("samples", samples);
        dbeImport.put("time_first", time_first);
        dbeImport.put("time_last", time_last);
        dbeImport.put("rej_cause", rej_cause);

        // Check that the LAC/CID is not already in the DBe_import (to avoid adding duplicate cells)
        String query = String.format(Locale.US,
                "SELECT LAC,CID FROM DBe_import WHERE LAC = %d AND CID = %d ",
                lac, cid);
        Cursor cursor = mDb.rawQuery(query, null);
        if (cursor.getCount() <= 0) { // <= 0 means cell is not in database yet
            mDb.insert("DBe_import", null, dbeImport);
        }
        cursor.close();
    }

    /**
     * Created this because we don't need to insert all the data in this table
     * since we don't yet have items like TMSI etc.
     */
    public void insertBTS(Cell cell) {

        // If LAC and CID are not already in DBi_bts, then add them.
        if (!cellInDbiBts(cell.getLAC(), cell.getCID())) {
            ContentValues values = new ContentValues();

            values.put("MCC", cell.getMCC());
            values.put("MNC", cell.getMNC());
            values.put("LAC", cell.getLAC());
            values.put("CID", cell.getCID());
            values.put("PSC", cell.getPSC());

            // TODO: setting these to 0 because if empty causing error in DB Restore
            values.put("T3212", 0);  // TODO
            values.put("A5x", 0);    // TODO
            values.put("ST_id", 0);  // TODO

            values.put("time_first", MiscUtils.getCurrentTimeStamp());
            values.put("time_last", MiscUtils.getCurrentTimeStamp());

            values.put("gps_lat", cell.getLat());  // TODO NO! These should be exact GPS from DBe_import or by manual addition!
            values.put("gps_lon", cell.getLon());  // TODO NO! These should be exact GPS from DBe_import or by manual addition!

            mDb.insert("DBi_bts", null, values);

            Log.i(TAG, mTAG + "DBi_bts was populated.");

        } else {
            // If cell is already in the DB, update it to last time seen and
            // update its GPS coordinates, if not 0.0
            ContentValues values = new ContentValues();
            values.put("time_last", MiscUtils.getCurrentTimeStamp());

            // TODO NO! These should be exact GPS from DBe_import or by manual addition!
            // Only update if GPS coordinates are good
            if (Double.doubleToRawLongBits(cell.getLat()) != 0
                    && Double.doubleToRawLongBits(cell.getLat()) != 0
                    && Double.doubleToRawLongBits(cell.getLon()) != 0
                    && Double.doubleToRawLongBits(cell.getLon()) != 0) {
                values.put("gps_lat", cell.getLat());
                values.put("gps_lon", cell.getLon());
            }

            // This is the native update equivalent to:
            // "UPDATE Dbi_bts time_last=...,gps_lat=..., gps_lon=... WHERE CID=..."
            // update (String table, ContentValues values, String whereClause, String[] whereArgs)
            mDb.update("DBi_bts", values, "CID=?", new String[]{Integer.toString(cell.getCID())});

            Log.i(TAG, mTAG + "DBi_bts updated: CID=" + cell.getCID() + " LAC=" + cell.getLAC());
        }

        // TODO: This doesn't make sense, if it's in DBi_bts it IS part of DBi_measure!
        // Checking to see if CID (now bts_id) is already in DBi_measure, if not add it.
        if (!cellInDbiMeasure(cell.getCID())) {
            ContentValues dbiMeasure = new ContentValues();

            dbiMeasure.put("bts_id", cell.getCID());                    // TODO: No!! Comment this out!
            dbiMeasure.put("nc_list", "no_data");                       // TODO: Better with "n/a" or "0", where are we getting this?
            dbiMeasure.put("time", MiscUtils.getCurrentTimeStamp());

            String slat = String.valueOf(cell.getLat());
            String slon = String.valueOf(cell.getLon());
            if (slat.isEmpty()) {
                slat = "0.0";
            }
            if (slon.isEmpty()) {
                slat = "0.0";
            }
            dbiMeasure.put("gpsd_lat", slat);
            dbiMeasure.put("gpsd_lon", slon);
            dbiMeasure.put("gpsd_accu", cell.getAccuracy());

            dbiMeasure.put("bb_power", "0");                            //TODO: This is not yet available, setting to "0"
            dbiMeasure.put("tx_power", "0");                             //TODO putting 0 here as we don't have this value yet
            dbiMeasure.put("rx_signal", String.valueOf(cell.getDBM())); //TODO putting cell.getDBM() here so we have some signal for OCID upload.
            dbiMeasure.put("RAT", String.valueOf(cell.getNetType()));
            dbiMeasure.put("TA", cell.getTimingAdvance());              //TODO does this actually get timing advance?
            dbiMeasure.put("BER", 0);                                    //TODO setting 0 because we don't have data yet.
            dbiMeasure.put("isSubmitted", 0);
            dbiMeasure.put("isNeighbour", 0);

            mDb.insert("DBi_measure", null, dbiMeasure);
            Log.i(TAG, mTAG + "DBi_measure inserted bts_id=" + cell.getCID());  // TODO: NO!!

        } else {
            // Updating DBi_measure tables if already exists.
            //TODO commented out items are because we don't have this data yet or it doesn't need updating
            ContentValues dbiMeasure = new ContentValues();
            //dbiMeasure.put("nc_list", nc_list);
            //dbiMeasure.put("time", MiscUtils.getCurrentTimeStamp());

            if (Double.doubleToRawLongBits(cell.getLat()) != 0
                    && Double.doubleToRawLongBits(cell.getLon()) != 0) {
                dbiMeasure.put("gpsd_lat", cell.getLat());
                dbiMeasure.put("gpsd_lon", cell.getLon());
            }
            if (Double.doubleToRawLongBits(cell.getAccuracy()) != 0
                    && cell.getAccuracy() > 0) {
                dbiMeasure.put("gpsd_accu", cell.getAccuracy());
            }

            //if(cell.getDBM() > 0) { //TODO: Wrong, bb_power is not DBM!
            //    dbiMeasure.put("bb_power", String.valueOf(cell.getDBM()));
            //}

            //dbiMeasure.put("bb_rf_temp",bb_rf_temp);

            //TODO set correct value for tx_power and rx_signal and un comment when working again
            //  if(cell.getRssi() >0) {
            //    dbiMeasure.put("tx_power", String.valueOf(cell.getRssi()));
            //    dbiMeasure.put("rx_signal",String.valueOf(cell.getRssi()));
            //}
            if (cell.getDBM() > 0) {
                dbiMeasure.put("rx_signal", String.valueOf(cell.getDBM())); // [dBm]
            }

            //dbiMeasure.put("rx_stype",rx_stype);
            //dbiMeasure.put("RAT", String.valueOf(cell.getNetType()));
            //dbiMeasure.put("BCCH",BCCH);
            //dbiMeasure.put("TMSI",TMSI);
            if (cell.getTimingAdvance() > 0) {
                dbiMeasure.put("TA", cell.getTimingAdvance());  // Only available on API >16 on LTE
            }

            //dbiMeasure.put("PD",PD);                          // TODO: LTE: I need to check this...
            //dbiMeasure.put("BER",0);                          // getGsmBitErrorRate() for API 17+
            // See: http://developer.android.com/reference/android/telephony/SignalStrength.html

            //dbiMeasure.put("AvgEcNo",AvgEcNo);                // TODO: I need to check this...

            mDb.update("DBi_measure", dbiMeasure, "bts_id=?", new String[]{Integer.toString(cell.getCID())});
            Log.i(TAG, mTAG + "DBi_measure updated bts_id=" + cell.getCID());

        }

    }


    /**
     * Inserts (API?) Cell Details into Database (DBi_bts)
     * Used be restoreDB()
     */
    // TODO: THESE ARE OUTDATED!! Please see design and update
    public void insertBTS(int mcc, int mnc, int lac, int cid, int psc, int t3212, int a5x,
                          int st_id, String time_first, String time_last, double lat, double lon) {

        if (cid != -1) {
            // Populate Content Values for INSERT or UPDATE
            ContentValues btsValues = new ContentValues();
            btsValues.put("MCC", mcc);
            btsValues.put("MNC", mnc);
            btsValues.put("LAC", lac);
            btsValues.put("CID", cid);
            btsValues.put("PSC", psc);
            btsValues.put("T3212", t3212);
            btsValues.put("A5x", a5x);
            btsValues.put("ST_id", st_id);
            btsValues.put("time_first", time_first);
            btsValues.put("time_last", time_last);
            btsValues.put("gps_lat", lat);
            btsValues.put("gps_lon", lon);

            // Only insert new cell if its not in DBi_bts
            if (!cellInDbiBts(lac, cid)) {
                mDb.insert("DBi_bts", null, btsValues);
            } else {
                // =======================================================================
                // TODO: EVA do I need to update an already known cell?
                // Good question! The DBi_bts is meant to be physical (non-volatile) and
                // stay the same, but I now see that PSC, T3212, A5x etc., might change,
                // under certain and special circumstances.
                // However, if they do, we should log this as an (suspicious?) event in EventLog
                // TODO: Addendum:
                // We could consider adding already known cells, if ANY of their parameters (not time)
                // has changed. At the moment this is only PSC, since we don't have the others...
                // =======================================================================
                mDb.update("DBi_bts", btsValues, "CID=?", new String[]{Integer.toString(cid)});
                Log.i(TAG, mTAG + "Warning: Physical cell data in DBi_bts has changed! CID=" + cid);
            }
        }

    }

    /**
     * Inserts a measurement into the DBi_measure and DBi_bts tables
     */
    public void insertDbiMeasure(int bts_id,
                                 String nc_list,
                                 String time,
                                 String gpsd_lat,
                                 String gpsd_lon,
                                 int gpsd_accuracy,
                                 String gpse_lat,
                                 String gpse_lon,
                                 String bb_power,
                                 String bb_rf_temp,
                                 String tx_power,
                                 String rx_signal,
                                 String rx_stype,
                                 String rat,
                                 String BCCH,
                                 String TMSI,
                                 int TA,
                                 int PD,
                                 int BER,
                                 String AvgEcNo,
                                 int isSubmitted,
                                 int isNeighbour) {

        // Check if a bts_id is already stored in DBi_measure. Only adds new cell if false
        // @banjaxbanjo:  No this is wrong... We should always add new measurements
        //
        if (cellInDbiMeasure(bts_id)) {
            ContentValues dbiMeasure = new ContentValues();

            dbiMeasure.put("bts_id", bts_id);    // TODO: comment this out!

            dbiMeasure.put("nc_list", nc_list);
            dbiMeasure.put("time", time);
            dbiMeasure.put("gpsd_lat", gpsd_lat);
            dbiMeasure.put("gpsd_lon", gpsd_lon);
            dbiMeasure.put("gpsd_accu", gpsd_accuracy);
            dbiMeasure.put("gpse_lat", gpse_lat);
            dbiMeasure.put("gpse_lon", gpse_lon);
            dbiMeasure.put("bb_power", bb_power);
            dbiMeasure.put("bb_rf_temp", bb_rf_temp);
            dbiMeasure.put("tx_power", tx_power);
            dbiMeasure.put("rx_signal", rx_signal);
            dbiMeasure.put("rx_stype", rx_stype);
            dbiMeasure.put("RAT", rat);
            dbiMeasure.put("BCCH", BCCH);
            dbiMeasure.put("TMSI", TMSI);
            dbiMeasure.put("TA", TA);
            dbiMeasure.put("PD", PD);
            dbiMeasure.put("BER", BER);
            dbiMeasure.put("AvgEcNo", AvgEcNo);
            dbiMeasure.put("isSubmitted", isSubmitted);
            dbiMeasure.put("isNeighbour", isNeighbour);

            mDb.insert("DBi_measure", null, dbiMeasure);
            // TODO: try with:
            // mDb.insert("DBi_measure, DBi_bts", null, dbiMeasure); // but you'll need the DBi_bts data then as well...
        }

    }

    /**
     * This inserts Detection Flag data that is used to fine tune the various
     * available detection mechanisms. (See Detection List in issue #230)
     *
     * There's also a CounterMeasure ID to link to possible future counter measures.
     */
    public void insertDetectionFlags(int code,
                                     String name,
                                     String description,
                                     int p1, int p2, int p3,
                                     double p1_fine, double p2_fine, double p3_fine,
                                     String app_text,
                                     String func_use,
                                     int istatus,
                                     int CM_id
    ) {

        ContentValues detectionFlags = new ContentValues();
        detectionFlags.put("code", code);
        detectionFlags.put("name", name);
        detectionFlags.put("description", description);
        detectionFlags.put("p1", p1);
        detectionFlags.put("p2", p2);
        detectionFlags.put("p3", p3);
        detectionFlags.put("p1_fine", p1_fine);
        detectionFlags.put("p2_fine", p2_fine);
        detectionFlags.put("p3_fine", p3_fine);
        detectionFlags.put("app_text", app_text);
        detectionFlags.put("func_use", func_use);
        detectionFlags.put("istatus", istatus);
        detectionFlags.put("CM_id", CM_id);

        mDb.insert("DetectionFlags", null, detectionFlags);
    }

    /**
     * Inserts log data into the EventLog table, using data provided by
     * the TelephonyManager (TM) or an already backed up EvenLog database...
     *
     * If you just need to add an event with currently connected TM data such
     * as CID,LAC,PSC,GPS, then use the simple version called toEventLog(),
     * defined below.
     *
     * DF_id   DF_desc
     * ---------------
     * 1       changing lac
     * 2       cell no in OCID
     * 3       "Detected Type-0 SMS"
     * 4       "Detected MWI SMS"
     * 5       "Detected WAP PUSH SMS"
     * 6       "Detected WAP PUSH (2) SMS"
     * 7 ...
     *
     * --------------------------------------------------------------------------
     * Old description
     * --------------------------------------------------------------------------
     * Description:     Inserts log data into the EventLog table
     *
     * Issues:          [ ] ALL events should be logged!!
     *                  [ ] To avoid repeated copies, only check last DB entries
     *                  [ ] Before inserting event, check that LAC/CID are not "-1".
     *                  [ ] Any related notifications are better put here as well, right?
     *                  [ ]
     *
     * Notes:           a)  Table item order:
     *
     *                      time,LAC,CID,PSC,gpsd_lat,gpsd_lon,gpsd_accu,DF_id,DF_desc
     *
     *                  b)  We need to check if cell is not in OCID  Events are not continuously
     *                      logged to the database as it currently stands. If the same cell shows
     *                      up it will again be dumped to the event log and will fill up pretty
     *                      quickly.
     *
     *                      @banjaxobanjo    What do you mean here? ALL events should be logged!
     *
     *                  c) To select last lines use:
     *
     *      SELECT * FROM EventLog WHERE LAC=30114 AND CID=779149 AND DF_id=1 ORDER BY _id DESC LIMIT 1;
     *      SELECT * FROM EventLog WHERE _id=(SELECT max(_id) FROM EventLog) AND LAC=30114 AND CID=779149 AND DF_id=1;
     *
     *
     *      Query examples for future devs:
     *
     *       SELECT * FROM EventLog WHERE CID = 1234 AND LAC = 4321 AND DF_id BETWEEN 1 AND 4
     *       SELECT * FROM EventLog WHERE CID = 1234 AND LAC = 4321 AND DF_id = 1" Changing LAC
     *       SELECT * FROM EventLog WHERE CID = 1234 AND LAC = 4321 AND DF_id = 2" Cell not in OCID
     *       SELECT * FROM EventLog WHERE CID = 1234 AND LAC = 4321 AND DF_id = 3" Detected SMS
     *       SELECT * FROM EventLog WHERE CID = 1234 AND LAC = 4321 AND DF_id = 4" Unknown T.B.A...     *
     */
    public void insertEventLog(String time,
                               int lac,
                               int cid,
                               int psc,
                               String gpsd_lat,
                               String gpsd_lon,
                               int gpsd_accu,
                               int DF_id,
                               String DF_description) {

        if (cid != -1) { // skip CID of "-1" (due to crappy API or roaming or Air-Plane Mode)
            // ONLY check if LAST entry is the same!
            String query = String.format(Locale.US,
                    // "SELECT * FROM EventLog WHERE LAC=%d AND CID=%d AND DF_id=%d ORDER BY _id DESC LIMIT 1",
                    "SELECT * from EventLog WHERE _id=(SELECT max(_id) from EventLog) AND CID=%d AND LAC=%d AND DF_id=%d",
                    // was: "SELECT * FROM EventLog WHERE CID = %d AND LAC = %d AND DF_id = %d",
                    cid, lac, DF_id);
            Cursor cursor = mDb.rawQuery(query, null);

            boolean insertData = true;
            if (cursor.getCount() > 0) {
                insertData = false;
            }
            cursor.close();

            if (insertData) {
                ContentValues eventLog = new ContentValues();

                eventLog.put("time", time);
                eventLog.put("LAC", lac);
                eventLog.put("CID", cid);
                eventLog.put("PSC", psc);
                eventLog.put("gpsd_lat", gpsd_lat);
                eventLog.put("gpsd_lon", gpsd_lon);
                eventLog.put("gpsd_accu", gpsd_accu);
                eventLog.put("DF_id", DF_id);
                eventLog.put("DF_description", DF_description);

                mDb.insert("EventLog", null, eventLog);
                Log.i(TAG, mTAG + "InsertEventLog(): Insert detection event into EventLog table with CID=" + cid);
            } else {
                // TODO This may need to be removed as it may spam the logcat buffer...
                Log.i(TAG, mTAG + "InsertEventLog(): Skipped inserting duplicate event into EventLog table with CID=" + cid);
            }
        }
    }


    // Defining a new simpler version of insertEventLog for use in CellTracker.
    // Please note, that in AMSICDDbAdapter (here) it is also used to backup DB,
    // in which case we can not use this simpler version!
    public void toEventLog(int DF_id, String DF_desc) {

        String time = MiscUtils.getCurrentTimeStamp();                    // time
        int lac = CellTracker.mMonitorCell.getLAC();                // LAC
        int cid = CellTracker.mMonitorCell.getCID();                // CID
        int psc = CellTracker.mMonitorCell.getPSC();                // PSC [UMTS,LTE]
        String gpsd_lat = String.valueOf(CellTracker.mMonitorCell.getLat()); // gpsd_lat
        String gpsd_lon = String.valueOf(CellTracker.mMonitorCell.getLon()); // gpsd_lon
        int gpsd_accu = (int) CellTracker.mMonitorCell.getAccuracy();        // gpsd_accu

        // skip CID/LAC of "-1" (due to crappy API, Roaming or Air-Plane Mode)
        if (cid != -1 || lac != -1) {
            // Check if LAST entry is the same!
            String query = String.format(Locale.US,
                    "SELECT * from EventLog WHERE _id=(SELECT max(_id) from EventLog) AND CID=%d AND LAC=%d AND PSC=%d AND DF_id=%d",
                    cid, lac, psc, DF_id);
            Cursor cursor = mDb.rawQuery(query, null);

            // WARNING: By skipping duplicate events, we might be missing counts of Type-0 SMS etc.
            boolean insertData = true;
            if (cursor.getCount() > 0) {
                insertData = false;
            }
            cursor.close();

            if (insertData) {
                ContentValues eventLog = new ContentValues();

                eventLog.put("time", time);                 // time
                eventLog.put("LAC", lac);                   // LAC
                eventLog.put("CID", cid);                   // CID
                eventLog.put("PSC", psc);                   // PSC [UMTS,LTE]
                eventLog.put("gpsd_lat", gpsd_lat);         // gpsd_lat
                eventLog.put("gpsd_lon", gpsd_lon);         // gpsd_lon
                eventLog.put("gpsd_accu", gpsd_accu);       // gpsd_accu
                eventLog.put("DF_id", DF_id);               // DF_id
                eventLog.put("DF_description", DF_desc);    // DF_desc

                mDb.insert("EventLog", null, eventLog);
                Log.i(TAG, mTAG + "ToEventLog(): Added new event: id=" + DF_id + " time=" + time + " cid=" + cid);

                // Short 100 ms Vibration
                // TODO not elegant solution, vibrator invocation should be moved somewhere else imho
                boolean vibrationEnabled = mPreferences.getBoolean(mContext.getString(zz.aimsicd.lite.R.string.pref_notification_vibrate_enable), true);
                int thresholdLevel = Integer.valueOf(mPreferences.getString(mContext.getString(zz.aimsicd.lite.R.string.pref_notification_vibrate_min_level), String.valueOf(Status.MEDIUM.ordinal())));
                boolean higherLevelThanThreshold = Status.MEDIUM.ordinal() <= thresholdLevel;

                if (vibrationEnabled && higherLevelThanThreshold) {
                    Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
                    v.vibrate(100);
                }

                // Short sound:
                // TODO see issue #15

            }
//            else {
            // TODO This may need to be removed as it may spam the logcat buffer...
            //Log.i(TAG, mTAG + mTAG + ":toEventLog(): Skipped inserting duplicate event");
//            }
        }
        // TODO This may need to be removed as it may spam the logcat buffer...
        //Log.i(TAG, mTAG + mTAG + ":insertEventLog(): Skipped inserting bad CID/LAC data");
    }


    /**
     * Inserts BTS Sector Type data into the SectorType table
     */
    public void insertSectorType(String description) {
        ContentValues sectorType = new ContentValues();
        sectorType.put("description", description);
        mDb.insert("SectorType", null, sectorType);
    }

    /**
     * Inserts SMS Detection Strings data into the "DetectionStrings" table
     */
    public void insertDetectionStrings(String det_str, String sms_type) {

        ContentValues detectionStrings = new ContentValues();
        detectionStrings.put("det_str", det_str);
        detectionStrings.put("sms_type", sms_type);

        String query = String.format(Locale.US,
                "SELECT * FROM DetectionStrings WHERE det_str = \"%s\" AND sms_type = \"%s\"",
                det_str, sms_type);

        // Check that the new string is not already in DB then insert
        Cursor cursor = mDb.rawQuery(query, null);
        if (cursor.getCount() <= 0) {
            mDb.insert("DetectionStrings", null, detectionStrings);
            cursor.close();
        } else {
            cursor.close();
        }
    }

    /**
     * Inserts detected silent SMS data into "SmsData" table
     */
    public void insertSmsData(String time,
                              String number,
                              String smsc,
                              String message,
                              String type,
                              String CLASS, //<-- had to put in uppercase class is used be api
                              int lac,
                              int cid,
                              String rat,
                              double gps_lat,
                              double gps_lon,
                              int isRoaming) {

        ContentValues smsData = new ContentValues();

        smsData.put("time", time);
        smsData.put("number", number);
        smsData.put("smsc", smsc);
        smsData.put("message", message);
        smsData.put("type", type);
        smsData.put("class", CLASS);
        smsData.put("lac", lac);
        smsData.put("cid", cid);
        smsData.put("rat", rat);
        smsData.put("gps_lat", gps_lat);
        smsData.put("gps_lon", gps_lon);
        smsData.put("isRoaming", isRoaming);

        // Check that SMS timestamp is not already in DB, then INSERT
        if (!isTimeStampInDB(time)) {
            mDb.insert("SmsData", null, smsData);
        }
    }

    /**
     * This checks if a cell with a given CID already exists in the (DBe_import) database.
     */
    public boolean openCellExists(int cellID) {
        String qry = String.format(Locale.US, "SELECT CID FROM DBe_import WHERE CID = %d", cellID);
        Cursor cursor = mDb.rawQuery(qry, null);
        boolean exists = cursor.getCount() > 0;
        cursor.close();
        return exists;
    }

    /**
     * Check if CID and LAC is already in DBi_bts
     * WARNING: This is only for checking, if used to get info, replace "CID,LAC" with "*"
     */
    public boolean cellInDbiBts(int lac, int cellID) {

        String query = String.format(Locale.US, "SELECT CID,LAC FROM DBi_bts WHERE LAC = %d AND CID = %d",
                lac, cellID);

        Cursor cursor = mDb.rawQuery(query, null);
        boolean exists = cursor.getCount() > 0;
        cursor.close();
        return exists;
    }

    /**
     * Check if CID (currently bts_id) is already in DBi_measure
     */
    public boolean cellInDbiMeasure(int cellID) {
        String query = String.format(Locale.US,
                // "SELECT CID FROM DBi_bts, DBi_measure WHERE bts_id = %d", // todo E:V:A
                "SELECT bts_id FROM DBi_measure WHERE bts_id = %d",
                cellID);

        Cursor cursor = mDb.rawQuery(query, null);
        boolean exists = cursor.getCount() > 0;
        cursor.close();
        return exists;
    }
} public class DrawerMenu {

    /**
     * Constants of id for NavDrawerItem
     * <p>Relates to {@link NavDrawerItem#getId()}<br />
     */
    public static class ID {


        /**
         * Constants of section of menu
         */
        public static final int SECTION_MAIN = 10;
        public static final int SECTION_DATABASE_SETTINGS = 30;
        public static final int SECTION_APPLICATION = 40;

        /**
         * Constants of item of 'main' section of menu
         */
        public static class MAIN {

            public static final int CURRENT_TREAT_LEVEL = 100; //Current Threat Level
            public static final int PHONE_SIM_DETAILS = 110; //Phone/SIM Details
            public static final int ALL_CURRENT_CELL_DETAILS = 120; //All Current Cell Details
            public static final int DB_VIEWER = 130; //Database Viewer
            public static final int ANTENNA_MAP_VIEW = 140; // Antenna Map View
            public static final int AT_COMMAND_INTERFACE = 150; //AT Command Interface

        }

        /**
         * Constants of item of 'settings' section of menu
         */
        public static class DATABASE_SETTINGS {

            public static final int BACKUP_DB = 310; //Backup DataBase
            public static final int RESTORE_DB = 320; //Restore DataBase
            public static final int RESET_DB = 330; //Reset DataBase
            //TODO need to implement
            public static final int EXPORT_DB_TO_CVS = 340; //Export DataBase to CSV files
            //TODO need to implement
            public static final int IMPORT_DB_FROM_CVS = 350; //Import DataBase from CSV file(s)

        }

        /**
         * Constants of item of 'application' section of menu
         */
        public static class APPLICATION {
            public static final int DOWNLOAD_LOCAL_BTS_DATA = 400; //Download Local BST Data FIXME     Is this should be "Download Local OCID Data" ?
            public static final int UPLOAD_LOCAL_BTS_DATA = 410; //Upload Local BST Data
            public static final int ADD_GET_OCID_API_KEY = 420; // Add/Get OCID API key
            public static final int FAQ = 450; // TODO Help/FAQ
            public static final int QUIT = 460; //Quit

        }

    }

    /**
     * The constant indicates the number of types of menu items
     */
    public static final int COUNT_OF_MENU_TYPE = 2;

} public class AimsicdService extends InjectionService {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";


    public static boolean isGPSchoiceChecked;
    public static final String GPS_REMEMBER_CHOICE = "remember choice";
    SharedPreferences gpsPreferences;

    // /data/data/com.SecUpwN.AIMSICD/shared_prefs/com.SecUpwN.AIMSICD_preferences.xml
    public static final String SHARED_PREFERENCES_BASENAME = "zz.aimsicd.lite_preferences";
    public static final String UPDATE_DISPLAY = "UPDATE_DISPLAY";

    /*
     * System and helper declarations
     */
    private final AimscidBinder mBinder = new AimscidBinder();
    private static final Handler timerHandler = new Handler();

    private CellTracker mCellTracker;
    private AccelerometerMonitor mAccelerometerMonitor;
    private SignalStrengthTracker signalStrengthTracker;
    private LocationTracker mLocationTracker;
    private RilExecutor mRilExecutor;
    private SmsDetector smsdetector;

    private boolean isLocationRequestShowing = false;

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public class AimscidBinder extends Binder {
        public AimsicdService getService() {
            return AimsicdService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        setTheme(R.style.AppTheme);

        signalStrengthTracker = new SignalStrengthTracker(getBaseContext());

        mAccelerometerMonitor = new AccelerometerMonitor(this, new Runnable() {
            @Override
            public void run() {
                // movement detected, so enable GPS
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        timerHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mLocationTracker.start();
                            }
                        });
                    }
                };
                new Thread(runnable).start();

                signalStrengthTracker.onSensorChanged();

                // check again in a while to see if GPS should be disabled
                // this runnable also re-enables this movement sensor
                timerHandler.postDelayed(batterySavingRunnable, AccelerometerMonitor.MOVEMENT_THRESHOLD_MS);
            }
        });

        mLocationTracker = new LocationTracker(this, mLocationListener);
        mRilExecutor = new RilExecutor(this);
        mCellTracker = new CellTracker(this, signalStrengthTracker);

        Log.i(TAG, mTAG + "Service launched successfully.");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        gpsPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        isGPSchoiceChecked = gpsPreferences.getBoolean(GPS_REMEMBER_CHOICE, false);
        return START_STICKY;

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mCellTracker.stop();
        mLocationTracker.stop();
        mAccelerometerMonitor.stop();
        mRilExecutor.stop();

        if (SmsDetector.getSmsDetectionState()) {
            smsdetector.stopSmsDetection();
        }
        Log.i(TAG, mTAG + "Service destroyed.");
    }

    public GeoLocation lastKnownLocation() {
        return mLocationTracker.lastKnownLocation();
    }

    public RilExecutor getRilExecutor() {
        return mRilExecutor;
    }

    public CellTracker getCellTracker() {
        return mCellTracker;
    }

    public Cell getCell() {
        return mCellTracker.getDevice().mCell;
    }

    public void setCell(Cell cell) {
        mCellTracker.getDevice().mCell = cell;
    }

    public boolean isTrackingCell() {
        return mCellTracker.isTrackingCell();
    }

    public boolean isMonitoringCell() {
        return mCellTracker.isMonitoringCell();
    }

    public void setCellMonitoring(boolean monitor) {
        mCellTracker.setCellMonitoring(monitor);
    }

    // SMS Detection Thread
    public boolean isSmsTracking() {
        return SmsDetector.getSmsDetectionState();
    }

    public void startSmsTracking() {
        if (!isSmsTracking()) {
            Log.i(TAG, mTAG + "Sms Detection Thread Started");
            smsdetector = new SmsDetector(this);
            smsdetector.startSmsDetection();
        }
    }

    public void stopSmsTracking() {
        if (isSmsTracking()) {
            smsdetector.stopSmsDetection();
            Log.i(TAG, mTAG + "Sms Detection Thread Stopped");
        }
    }

    // while tracking a cell, manage the power usage by switching off GPS if no movement
    private final Runnable batterySavingRunnable = new Runnable() {
        @Override
        public void run() {
            if (mCellTracker.isTrackingCell()) {
                // if no movement in a while, shut off GPS. Gets re-enabled when there is movement
                if (mAccelerometerMonitor.notMovedInAWhile() ||
                        mLocationTracker.notMovedInAWhile()) {
                    mLocationTracker.stop();
                }
                mAccelerometerMonitor.start();
            }
        }
    };

    /**
     * Cell Information Tracking and database logging
     *
     * @param track Enable/Disable tracking
     */
    public void setCellTracking(boolean track) {
        mCellTracker.setCellTracking(track);

        if (track) {
            mLocationTracker.start();
            mAccelerometerMonitor.start();
        } else {
            mLocationTracker.stop();
            mAccelerometerMonitor.stop();
        }
    }

    public void checkLocationServices() {
        if (mCellTracker.isTrackingCell() && !mLocationTracker.isGPSOn() && !isGPSchoiceChecked) {
            enableLocationServices();
        }
    }

    private void enableLocationServices() {
        if (isLocationRequestShowing) {
            return; // only show dialog once
        }

        LayoutInflater dialogInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
        // https://possiblemobile.com/2013/05/layout-inflation-as-intended/
        // This is an AlertDialog so null is ok:
        @SuppressLint("InflateParams")
        View dialogView = dialogInflater.inflate(R.layout.dialog_request_gps, null, false);
        CheckBox rememberChoice = (CheckBox) dialogView.findViewById(R.id.check_choice);
        Button notNow = (Button) dialogView.findViewById(R.id.not_now_button);
        Button enableGPS = (Button) dialogView.findViewById(R.id.enable_gps_button);

        final AlertDialog alertDialog = new AlertDialog.Builder(this)
                /*.setMessage(R.string.location_error_message)
                .setTitle(R.string.location_error_title)*/
                .setView(dialogView)
                .setCancelable(false)
                /*.setPositiveButton(R.string.text_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        isLocationRequestShowing = false;
                        Intent gpsSettings = new Intent(
                                android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                        gpsSettings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(gpsSettings);
                    }
                })
                .setNegativeButton(R.string.text_cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        isLocationRequestShowing = false;
                        setCellTracking(false);
                    }
                })*/
                .create();

        rememberChoice.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                if (isChecked) {
                    isGPSchoiceChecked = true;
                    gpsPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                    SharedPreferences.Editor editor = gpsPreferences.edit();
                    editor.putBoolean(GPS_REMEMBER_CHOICE, isGPSchoiceChecked);
                    editor.apply();

                } else {
                    isGPSchoiceChecked = false;
                }
            }
        });

        notNow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                isLocationRequestShowing = false;
                setCellTracking(false);
                alertDialog.cancel();
                alertDialog.dismiss();
            }
        });

        enableGPS.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                isLocationRequestShowing = false;
                Intent gpsSettings = new Intent(
                        android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                gpsSettings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                alertDialog.cancel();
                alertDialog.dismiss();
                startActivity(gpsSettings);
            }
        });

        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        alertDialog.show();
        isLocationRequestShowing = true;
    }

    LocationListener mLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location loc) {
            timerHandler.postDelayed(batterySavingRunnable, AccelerometerMonitor.MOVEMENT_THRESHOLD_MS);
            mCellTracker.onLocationChanged(loc);
        }

        @Override
        public void onProviderDisabled(String provider) {
            if (mCellTracker.isTrackingCell() && provider.equals(LocationManager.GPS_PROVIDER) && !isGPSchoiceChecked) {
                enableLocationServices();
            }
        }

        @Override
        public void onProviderEnabled(String s) {

        }

        @Override
        public void onStatusChanged(String s, int i, Bundle bundle) {

        }
    };
} public class CellTracker implements SharedPreferences.OnSharedPreferenceChangeListener {

    public static final String TAG = "AICDL";
    public static final String mTAG = "CellTracker: ";

    public static Cell mMonitorCell;
    public static String OCID_API_KEY = null;   // see getOcidKey()
    public static int PHONE_TYPE;               //
    public static int LAST_DB_BACKUP_VERSION;   //
    public static long REFRESH_RATE;            // [s] The DeviceInfo refresh rate (arrays.xml)
    public static final String SILENT_SMS = "SILENT_SMS_DETECTED";

    private boolean CELL_TABLE_CLEANSED;        // default is FALSE for "boolean", and NULL for "Boolean".
    private final int NOTIFICATION_ID = 1;
    private final Device mDevice = new Device();

    private static TelephonyManager tm;
    private final SignalStrengthTracker signalStrengthTracker;
    private PhoneStateListener mPhoneStateListener;
    private SharedPreferences prefs;
    private TinyDB tinydb; // Used to simplify SharedPreferences usage above

    //=====================================================
    //  Tracking and Alert Declarations
    //=====================================================
    private boolean mMonitoringCell;
    private boolean mTrackingCell;
    private boolean mChangedLAC;
    private boolean mCellIdNotInOpenDb;
    private boolean mTypeZeroSmsDetected;
    private boolean mVibrateEnabled;
    private int mVibrateMinThreatLevel;
    private LinkedBlockingQueue<NeighboringCellInfo> neighboringCellBlockingQueue;

    private final AIMSICDDbAdapter dbHelper;
    private static Context context;

    public CellTracker(Context context, SignalStrengthTracker sst) {
        this.context = context;
        this.signalStrengthTracker = sst;

        // Creating tinydb here to avoid: "TinyDb tinydb = new TinyDb(context);"
        // every time we need to use tinydb in this class.
        tinydb = TinyDB.getInstance();

        // TelephonyManager provides system details
        tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

        // Shared Preferences
        prefs = context.getSharedPreferences(AimsicdService.SHARED_PREFERENCES_BASENAME, 0);
        prefs.registerOnSharedPreferenceChangeListener(this);
        loadPreferences();
        setNotification();

        PHONE_TYPE = tm.getPhoneType(); // PHONE_TYPE_GSM/CDMA/SIP/NONE

        dbHelper = new AIMSICDDbAdapter(context);

        // Remove all but the last DBi_bts entry, after:
        //   (a) starting CellTracker for the first time or
        //   (b) having cleared the preferences.
        // Subsequent runs are prevented by a hidden boolean preference. See: loadPreferences()
        if (!CELL_TABLE_CLEANSED) {
            dbHelper.cleanseCellTable();
            SharedPreferences.Editor prefsEditor;
            prefsEditor = prefs.edit();
            prefsEditor.putBoolean(context.getString(R.string.pref_cell_table_cleansed), true); // set to true
            prefsEditor.apply();
        }
        mDevice.refreshDeviceInfo(tm, context); // Telephony Manager
        mMonitorCell = new Cell();
    }

    public boolean isTrackingCell() {
        return mTrackingCell;
    }

    public boolean isMonitoringCell() {
        return mMonitoringCell;
    }


    /**
     * Description:     Cell Information Monitoring
     *                  TODO: What exactly are we monitoring here??
     *                  TODO: Is this related to the Tracking/Monitoring in the menu drawer?
     *
     * @param monitor Enable/Disable monitoring
     */
    public void setCellMonitoring(boolean monitor) {
        if (monitor) {
            mMonitoringCell = true;
            Helpers.msgShort(context, context.getString(R.string.monitoring_cell_information));
        } else {
            mMonitoringCell = false;
            Helpers.msgShort(context, context.getString(R.string.stopped_monitoring_cell_information));
        }
        setNotification();
    }

    public Device getDevice() {
        return mDevice;
    }



    public void stop() {
        if (isMonitoringCell()) {
            setCellMonitoring(false);
        }
        if (isTrackingCell()) {
            setCellTracking(false);
        }

        cancelNotification();
        tm.listen(mCellSignalListener, PhoneStateListener.LISTEN_NONE);
        prefs.unregisterOnSharedPreferenceChangeListener(this);

    }

    /**
     *  Description:    Cell Information Tracking and database logging
     *
     *          TODO: update this!!
     *
     *          If the "tracking" option is enabled (as it is by default) then we are keeping
     *          a record (tracking) of the device location "gpsd_lat/lon", the connection
     *          signal strength (rx_signal) and data activity (?) and data connection state (?).
     *
     *          The items included in these are stored in the "DBi_measure" table.
     *
     *          DATA_ACTIVITY:
     *          DATA_CONNECTION_STATE:
     *
     *
     *  UI/function:        Drawer:  "Toggle Cell Tracking"
     *
     *  Issues:
     *
     *  Notes:              TODO:   We also need to listen and log for:
     *
     *      [ ]     LISTEN_CALL_STATE:
     *                  CALL_STATE_IDLE
     *                  CALL_STATE_OFFHOOK
     *                  CALL_STATE_RINGING
     *
     *      [ ]     LISTEN_SERVICE_STATE:
     *                  STATE_EMERGENCY_ONLY
     *                  STATE_IN_SERVICE
     *                  STATE_OUT_OF_SERVICE
     *                  STATE_POWER_OFF
     *
     * @param track Enable/Disable tracking
     */
    public void setCellTracking(boolean track) {
        if (track) {
            tm.listen(mCellSignalListener,
                    PhoneStateListener.LISTEN_CELL_LOCATION |         // gpsd_lat/lon ?
                    PhoneStateListener.LISTEN_SIGNAL_STRENGTHS |      // rx_signal
                    PhoneStateListener.LISTEN_DATA_ACTIVITY |         // No,In,Ou,IO,Do
                    PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | // Di,Ct,Cd,Su
                    PhoneStateListener.LISTEN_CELL_INFO               // !? (Need API 17)
            );
            mTrackingCell = true;
            Helpers.msgShort(context, context.getString(R.string.tracking_cell_information));
        } else {
            tm.listen(mCellSignalListener, PhoneStateListener.LISTEN_NONE);
            mDevice.mCell.setLon(0.0);
            mDevice.mCell.setLat(0.0);
            mDevice.setCellInfo("[0,0]|nn|nn|"); //default entries into "locationinfo"::Connection
            mTrackingCell = false;
            Helpers.msgShort(context, context.getString(R.string.stopped_tracking_cell_information));
        }
        setNotification();
    }

    /**
     *  Description:    This handles the settings/choices and default preferences, when changed.
     *                  From the default file:
     *                          preferences.xml
     *                  And saved in the file:
     *                          /data/data/com.SecUpwN.AIMSICD/shared_prefs/zz.aimsicd.lite_preferences.xml
     *
     *  NOTE:           - For more code transparency we have added TinyDB.java as a
     *                    wrapper to SharedPreferences usage. Please try to use this instead.
     *
     * @param sharedPreferences
     * @param key
     */
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        final String KEY_UI_ICONS = context.getString(R.string.pref_ui_icons_key);
        final String REFRESH = context.getString(R.string.pref_refresh_key);
        final String DB_VERSION = context.getString(R.string.pref_last_database_backup_version);
        final String OCID_KEY = context.getString(R.string.pref_ocid_key);
        final String VIBRATE_ENABLE = context.getString(R.string.pref_notification_vibrate_enable);
        final String VIBRATE_MIN_LEVEL = context.getString(R.string.pref_notification_vibrate_min_level);

        if (key.equals(KEY_UI_ICONS)) {
            // Update Notification to display selected icon type
            setNotification();
        } else if (key.equals(REFRESH)) {
            String refreshRate = sharedPreferences.getString(REFRESH, "1");
            if (refreshRate.isEmpty()) {
                refreshRate = "1"; // Set default to: 1 second
            }

            int rate = Integer.parseInt(refreshRate);
            long t;
            switch (rate) {
                case 1:
                    t = 15L; // Automatic refresh rate is 15 seconds
                    break;
                default:
                    t = (long) rate; // Default is 1 sec (from above)
                    break;
            }
            REFRESH_RATE = TimeUnit.SECONDS.toMillis(t);
        } else if (key.equals(DB_VERSION)) {
            LAST_DB_BACKUP_VERSION = sharedPreferences.getInt(DB_VERSION, 1);
        } else if (key.equals(OCID_KEY)) {
            getOcidKey();
        } else if (key.equals(VIBRATE_ENABLE)) {
            mVibrateEnabled = sharedPreferences.getBoolean(VIBRATE_ENABLE, true);
        } else if (key.equals(VIBRATE_MIN_LEVEL)) {
            mVibrateMinThreatLevel = Integer.valueOf(sharedPreferences.getString(VIBRATE_MIN_LEVEL, String.valueOf(Status.MEDIUM.ordinal())));
        }
    }

    public void getOcidKey() {
        final String OCID_KEY = context.getString(R.string.pref_ocid_key);
        OCID_API_KEY = prefs.getString(OCID_KEY, BuildConfig.OPEN_CELLID_API_KEY);
        //if (OCID_API_KEY == null) {
        if (OCID_API_KEY.isEmpty()) {
            OCID_API_KEY = "NA";
        }
    }

    /**
     *  Description:    Updates Neighbouring Cell details
     *
     *                  TODO: add more details...
     *
     *
     */
    public List<Cell> updateNeighbouringCells() {
        List<Cell> neighboringCells = new ArrayList<>();
        List<NeighboringCellInfo> neighboringCellInfo = tm.getNeighboringCellInfo();

        if (neighboringCellInfo == null) {
            neighboringCellInfo = new ArrayList<>();
        }

        Boolean nclp = tinydb.getBoolean("nc_list_present"); // Is NC list present? (default is false)

        //if nclp = true then check for neighboringCellInfo
        //if (neighboringCellInfo != null && neighboringCellInfo.size() == 0 && nclp) {
        //if (!neighboringCellInfo.isEmpty() && neighboringCellInfo.size() == 0 && nclp) {
        if (neighboringCellInfo.size() == 0 && nclp) {

            Log.i(TAG, mTAG + "NeighbouringCellInfo is empty: start polling...");

            // Try to poll the neighboring cells for a few seconds
            neighboringCellBlockingQueue = new LinkedBlockingQueue<>(100); // Max 100 NC's before blocking (?)

            // ToDo: rename... (We don't use API's <18 anymore.)
            DeviceApi18.startListening(tm, phoneStatelistener);

            // ToDo: Move or remove. This should now be done by getAllCellInfo() in new RadioAPI "collector" module.
            for (int i = 0; i < 10 && neighboringCellInfo.size() == 0; i++) {
                try {
                    Log.d(TAG, mTAG + "NeighbouringCellInfo empty: trying " + i);
                    NeighboringCellInfo info = neighboringCellBlockingQueue.poll(1, TimeUnit.SECONDS);
                    if (info == null) {
                        neighboringCellInfo = tm.getNeighboringCellInfo();
                        if (neighboringCellInfo != null) {
                            if (neighboringCellInfo.size() > 0) {
                                // Can we think of a better log message here?
                                Log.d(TAG, mTAG + "NeighbouringCellInfo found on " + i + " try. (time based)");
                                break;
                            } else {
                                continue;
                            }
                        }
                    }
                    List<NeighboringCellInfo> cellInfoList =
                            new ArrayList<>(neighboringCellBlockingQueue.size() + 1);
                    while (info != null) {
                        cellInfoList.add(info);
                        info = neighboringCellBlockingQueue.poll(1, TimeUnit.SECONDS);
                    }
                    neighboringCellInfo = cellInfoList;
                } catch (InterruptedException e) {
                    Log.e(TAG, mTAG + "Interrupted BlockingQueue Exception: " + e);
                }
            }
        }

        //log.debug(mTAG + ": neighbouringCellInfo size: " + neighboringCellInfo.size());

        // Add NC list to DBi_measure:nc_list
        for (NeighboringCellInfo neighbourCell : neighboringCellInfo) {
            Log.i(TAG, mTAG + "NeighbouringCellInfo -" +
                    " LAC:" + neighbourCell.getLac() +
                    " CID:" + neighbourCell.getCid() +
                    " PSC:" + neighbourCell.getPsc() +
                    " RSSI:" + neighbourCell.getRssi());

            final Cell cell = new Cell(
                    neighbourCell.getCid(),
                    neighbourCell.getLac(),
                    neighbourCell.getRssi(),
                    neighbourCell.getPsc(),
                    neighbourCell.getNetworkType(), false);
            neighboringCells.add(cell);
        }
        return neighboringCells;
    }


    /**
     *  Description:    This snippet sets a global variable (SharedPreference) to indicate
     *                  if Neighboring cells info CAN be obtained or has been obtained
     *                  previously. If it has been and suddenly there are none, we can
     *                  raise a flag of CID being suspicious.
     *
     *                  The logic is:
     *
     *                      IF NC has never been seen on device:
     *                          - NC list is NOT supported on this AOS/HW, so we do nothing.
     *                      IF NC has been seen before,
     *                          - NC list IS supported on this AOS/HW, so we set:
     *                              nc_list_present : "true"
     *                      IF NC list has been seen before AND current CID doesn't provide
     *                      one, we raise an alarm or flag.
     *
     *
     *  Notes:      a)  Not sure where to place this test, but let's try it here..
     *              b)  In TinyDB, the getBoolean() returns "false" by default, if empty.
     *
     *              c)   This will be called on every cell change (ref:  issue #346)
     *              d)  *** https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/383
     *
     *  Issue:
     *          [ ] We need a timer or "something" to reverse a positive detection once
     *              we're out and away from the fake BTS cell.
     *          [ ] We need to add this to EventLog
     *          [ ] We need to add detection tickers etc...
     *          [ ] Attention to the spelling of "neighbor" (USA) Vs. "neighbour" (Eng world)
     *          [x] We need to use a global and persistent variable and not a system property
     *
     */
    public void checkForNeighbourCount(CellLocation location) {
        Log.i(TAG, mTAG + "CheckForNeighbourCount()");

        Integer ncls = 0;                                        // NC list size
        if (tm != null && tm.getNeighboringCellInfo() != null) { // See # 383
            ncls = tm.getNeighboringCellInfo().size();
        }
        Boolean nclp = tinydb.getBoolean("nc_list_present");    // NC list present? (default is false)

        if (ncls > 0) {
           Log.d(TAG, mTAG + "NeighbouringCellInfo size: " + ncls);
            if (!nclp) {
               Log.d(TAG, mTAG + "Setting nc_list_present to: true");
                tinydb.putBoolean("nc_list_present", true);
            }
        } else if (ncls == 0 && nclp) {
            // Detection 7a
            Log.i(TAG, mTAG + "ALERT: No neighboring cells detected for CID: " + mDevice.mCell.getCID());
            vibrate(100, Status.MEDIUM);
            dbHelper.toEventLog(4, "No neighboring cells detected"); // (DF_id, DF_desc)
        } else  {
            // Todo: remove cid string when working.
            Log.d(TAG, mTAG + "NC list not supported by AOS on this device. Nothing to do.");
            Log.d(TAG, mTAG + ": Setting nc_list_present to: false");
            tinydb.putBoolean("nc_list_present", false);
        }
    }

    /**
     *          I removed the timer that activated this code and now the code will be run when
     *          the cell changes so it will detect faster rather than using a timer that might
     *          miss an imsi catcher, also says cpu rather than refreshing every x seconds.
     *
     *          original comments below from xLaMbChOpSx
     *
     *
     *  Description:    (From xLaMbChOpSx commit comment)
     *
     *      Initial implementation for detection method 1 to compare the CID & LAC with the Cell
     *      Information Table contents as an initial implementation for detection of a changed LAC,
     *      once OCID issues (API key use etc) have been finalised this detection method can be
     *      extended to include checking of external data.
     *
     *      REMOVED: refresh timer info
     *
     *      As I have no real way of testing this I require testing by other project members who
     *      do have access to equipment or an environment where a changing LAC can be simulated
     *      thus confirming the accuracy of this implementation.
     *
     *      Presently this will only invoke the MEDIUM threat level through the notification and
     *      does not fully implement the capturing and score based method as per the issue details
     *      once further testing is complete the alert and tracking of information can be refined.
     *
     *      See:
     *        https://github.com/xLaMbChOpSx/Android-IMSI-Catcher-Detector/commit/43ae77e2a0cad10dfd50f92da5a998f9ece95b38
     *        https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/91#issuecomment-64391732
     *
     *  Short explanation:
     *
     *                  This is a polling mechanism for getting the LAC/CID and location
     *                  info for the currently connected cell.
     *
     *  Variables:
     *                  FIXED: now updates on cell change rather than a timer
     *                  There is a "timer" here (REFRESH_RATE), what exactly is it timing?
     *                  "Every REFRESH_RATE seconds, get connected cell details."
     *
     *  Notes:
     *              a) Check if CellID (CID) is in DBe_import (OpenCell) database (issue #91)
     *                 See news in: issue #290 and compare to AIMSICDDbAdapter.java
     *
     *  Issues:     [ ] We shouldn't do any detection here!
     *              [ ] We might wanna use a listener to do this?
     *                  Are there any reasons why not using a listener?
     *
     *  ChangeLog:
     *              2015-03-03  E:V:A           Changed getProp() to use TinyDB (SharedPreferences)
     *              2015-0x-xx  banjaxbanjo     Update: ??? (hey dude what did you do?)
     *
     */
    public void compareLac(CellLocation location) {
        switch (mDevice.getPhoneID()) {

            case TelephonyManager.PHONE_TYPE_NONE:
            case TelephonyManager.PHONE_TYPE_SIP:
            case TelephonyManager.PHONE_TYPE_GSM:
                GsmCellLocation gsmCellLocation = (GsmCellLocation) location;
                if (gsmCellLocation != null) {
                    mMonitorCell.setLAC(gsmCellLocation.getLac());
                    mMonitorCell.setCID(gsmCellLocation.getCid());

                    // Check if LAC is ok
                    boolean lacOK = dbHelper.checkLAC(mMonitorCell);
                    if (!lacOK) {
                        mChangedLAC = true;
                        dbHelper.toEventLog(1, "Changing LAC");

                        // Detection Logs are made in checkLAC()
                        vibrate(100, Status.MEDIUM);
                    } else {
                        mChangedLAC = false;
                    }

                    // Check if CID is in DBe_import DB (issue #91)
                    if (tinydb.getBoolean("ocid_downloaded")) {
                        if (!dbHelper.openCellExists(mMonitorCell.getCID())) {
                            dbHelper.toEventLog(2, "CID not in DBe_import");

                            Log.i(TAG, mTAG + "ALERT: Connected to unknown CID not in DBe_import: " + mMonitorCell.getCID());
                            vibrate(100, Status.MEDIUM);

                            mCellIdNotInOpenDb = true;
                        } else {
                            mCellIdNotInOpenDb = false;
                        }
                    }
                }
                break;

            case TelephonyManager.PHONE_TYPE_CDMA:
                CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) location;
                if (cdmaCellLocation != null) {
                    mMonitorCell.setLAC(cdmaCellLocation.getNetworkId());
                    mMonitorCell.setCID(cdmaCellLocation.getBaseStationId());

                    boolean lacOK = dbHelper.checkLAC(mMonitorCell);
                    if (!lacOK) {
                        mChangedLAC = true;
                        /*dbHelper.insertEventLog(
                                MiscUtils.getCurrentTimeStamp(),
                                mMonitorCell.getLAC(),
                                mMonitorCell.getCID(),
                                mMonitorCell.getPSC(),//This is giving weird values like 21478364... is this right?
                                String.valueOf(mMonitorCell.getLat()),
                                String.valueOf(mMonitorCell.getLon()),
                                (int)mMonitorCell.getAccuracy(),
                                1,
                                "Changing LAC"
                        );*/
                        dbHelper.toEventLog(1, "Changing LAC");
                    } else {
                        mChangedLAC = false;
                    }
                }
        }
        setNotification();
    }

    /**
     * Check device's current cell location's LAC against local database AND verify cell's CID
     * exists in the OCID database.
     *
     * @see #compareLac(CellLocation)
     */
    public void compareLacAndOpenDb() {
        compareLac(tm.getCellLocation());
    }

    // Where is this used?
    private void handlePhoneStateChange() {
        List<NeighboringCellInfo> neighboringCellInfo = tm.getNeighboringCellInfo();
        if (neighboringCellInfo == null || neighboringCellInfo.size() == 0) {
            return;
        }

        Log.i(TAG, mTAG + "NeighbouringCellInfo empty - event based polling succeeded!");
        tm.listen(phoneStatelistener, PhoneStateListener.LISTEN_NONE);
        if (neighboringCellInfo == null) {
            neighboringCellInfo = new ArrayList<>();
        }
        neighboringCellBlockingQueue.addAll(neighboringCellInfo);
    }

    public void refreshDevice() {
        mDevice.refreshDeviceInfo(tm, context);
    }

    /**
     * Description:     Process User Preferences
     *                  This loads the default Settings/Preferences as set in:
     *                      preferences.xml
     *                  and:
     *                      /data/data/com.SecUpwN.AIMSICD/shared_prefs/com.SecUpwN.AIMSICD_preferences.xml
     *
     *                  TODO: Please add more info
     *
     */
    private void loadPreferences() {
        // defaults are given by:  getBoolean(key, default if not exist)
        boolean trackFemtoPref  = prefs.getBoolean(context.getString(R.string.pref_femto_detection_key), false);
        boolean trackCellPref   = prefs.getBoolean(context.getString(R.string.pref_enable_cell_key), true);
        boolean monitorCellPref = prefs.getBoolean(context.getString(R.string.pref_enable_cell_monitoring_key), true);

        LAST_DB_BACKUP_VERSION      = prefs.getInt(context.getString(R.string.pref_last_database_backup_version), 1);
        CELL_TABLE_CLEANSED         = prefs.getBoolean(context.getString(R.string.pref_cell_table_cleansed), false);
        String refreshRate = prefs.getString(context.getString(R.string.pref_refresh_key), "1");
        this.mVibrateEnabled = prefs.getBoolean(context.getString(R.string.pref_notification_vibrate_enable), true);
        this.mVibrateMinThreatLevel = Integer.valueOf(prefs.getString(context.getString(R.string.pref_notification_vibrate_min_level), String.valueOf(Status.MEDIUM.ordinal())));

        // Default to Automatic ("1")
        if (refreshRate.isEmpty()) {
            refreshRate = "1";
        }

        int rate = Integer.parseInt(refreshRate);
        long t;
        if (rate == 1) {
            t = 15L; // Automatic refresh rate is 15 seconds
        } else {
            t = ((long) rate); // Default is 1 sec (from above)
        }

        REFRESH_RATE = TimeUnit.SECONDS.toMillis(t);
        getOcidKey();

        if (trackCellPref) {
            setCellTracking(true);
        }
        if (monitorCellPref) {
            setCellMonitoring(true);
        }
    }


    /**
     * Description:  TODO: add more info
     *
     *    This SEEM TO add entries to the "locationinfo" DB table in the ??
     *
     *    Issues:
     *
     *    [ ] We see that "Connection" items are messed up. What is the purpose of these?
     *
     *    $ sqlite3.exe -header -csv aimsicd.db 'select * from locationinfo;'
     *      _id,Lac,CellID,Net,Lat,Lng,Signal,Connection,Timestamp
     *      1,10401,6828111,10, 54.6787,25.2869, 24, "[10401,6828111,126]No|Di|HSPA|", "2015-01-21 20:45:10"
     *
     *    [ ] TODO: CDMA has to extract the MCC and MNC using something like:
     *
     *      String mccMnc = phoneMgr.getNetworkOperator();
     *      String cdmaMcc = "";
     *      String cdmaMnc = "";
     *      if (mccMnc != null && mccMnc.length() >= 5) {
     *          cdmaMcc = mccMnc.substring(0, 3);
     *          cdmaMnc = mccMnc.substring(3, 5);
     }      }
     *
     *    ChangeLog:
     *
     *          2015-01-22  E:V:A   Changed what appears to be a typo in the character
     *                              following getNetworkTypeName(), "|" to "]"
     *          2015-01-24  E:V:A   FC WTF!? Changed back ^ to "|". (Where is this info parsed?)
     *
     */
    private final PhoneStateListener mCellSignalListener = new PhoneStateListener() {
        public void onCellLocationChanged(CellLocation location) {

            checkForNeighbourCount(location);
            compareLac(location);
            refreshDevice();
            mDevice.setNetID(tm);
            mDevice.getNetworkTypeName();

            switch (mDevice.getPhoneID()) {

                case TelephonyManager.PHONE_TYPE_NONE:
                case TelephonyManager.PHONE_TYPE_SIP:
                case TelephonyManager.PHONE_TYPE_GSM:
                    GsmCellLocation gsmCellLocation = (GsmCellLocation) location;
                    if (gsmCellLocation != null) {
                        //TODO @EVA where are we sending this setCellInfo data?

                        //TODO
                        /*@EVA
                            Is it a good idea to dump all cells to db because if we spot a known cell
                            with different lac then this will also be dump to db.

                        */
                        mDevice.setCellInfo(
                                gsmCellLocation.toString() +                // ??
                                mDevice.getDataActivityTypeShort() + "|" +  // No,In,Ou,IO,Do
                                mDevice.getDataStateShort() + "|" +         // Di,Ct,Cd,Su
                                mDevice.getNetworkTypeName() + "|"          // HSPA,LTE etc
                        );

                        mDevice.mCell.setLAC(gsmCellLocation.getLac());     // LAC
                        mDevice.mCell.setCID(gsmCellLocation.getCid());     // CID
                        if (gsmCellLocation.getPsc() != -1) {
                            mDevice.mCell.setPSC(gsmCellLocation.getPsc()); // PSC
                        }

                        /*
                            Add cell if gps is not enabled
                            when gps enabled lat lon will be updated
                            by function below

                         */
                    }
                    break;

                case TelephonyManager.PHONE_TYPE_CDMA:
                    CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) location;
                    if (cdmaCellLocation != null) {
                        mDevice.setCellInfo(
                                cdmaCellLocation.toString() +                       // ??
                                mDevice.getDataActivityTypeShort() + "|" +  // No,In,Ou,IO,Do
                                mDevice.getDataStateShort() + "|" +         // Di,Ct,Cd,Su
                                mDevice.getNetworkTypeName() + "|"          // HSPA,LTE etc
                        );
                        mDevice.mCell.setLAC(cdmaCellLocation.getNetworkId());      // NID
                        mDevice.mCell.setCID(cdmaCellLocation.getBaseStationId());  // BID
                        mDevice.mCell.setSID(cdmaCellLocation.getSystemId());       // SID
                        mDevice.mCell.setMNC(cdmaCellLocation.getSystemId());       // MNC <== BUG!??
                        mDevice.setNetworkName(tm.getNetworkOperatorName());        // ??
                    }
            }

        }

        /**
         *  Description:  TODO: add more info
         *
         *  Issues:
         *
         *      [ ]     Getting and comparing signal strengths between different RATs can be very
         *              tricky, since they all return different ranges of values. AOS doesn't
         *              specify very clearly what exactly is returned, even though people have
         *              a good idea, by trial and error.
         *
         *              See note in : SignalStrengthTracker.java
         *
         *  Notes:
         *
         *
         *
         */
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            // Update Signal Strength
            if (signalStrength.isGsm()) {
                int dbm;
                if (signalStrength.getGsmSignalStrength() <= 2 ||
                        signalStrength.getGsmSignalStrength() == NeighboringCellInfo.UNKNOWN_RSSI) {
                    // Unknown signal strength, get it another way
                    String[] bits = signalStrength.toString().split(" ");
                    dbm = Integer.parseInt(bits[9]);
                } else {
                    dbm = signalStrength.getGsmSignalStrength();
                }
                mDevice.setSignalDbm(dbm);
            } else {
                int evdoDbm = signalStrength.getEvdoDbm();
                int cdmaDbm = signalStrength.getCdmaDbm();

                // Use lowest signal to be conservative
                mDevice.setSignalDbm((cdmaDbm < evdoDbm) ? cdmaDbm : evdoDbm);
            }
            // Send it to signal tracker
            signalStrengthTracker.registerSignalStrength(mDevice.mCell.getCID(), mDevice.getSignalDBm());
            //signalStrengthTracker.isMysterious(mDevice.mCell.getCID(), mDevice.getSignalDBm());
        }

        // In DB:   No,In,Ou,IO,Do
        public void onDataActivity(int direction) {
            switch (direction) {
                case TelephonyManager.DATA_ACTIVITY_NONE:
                    mDevice.setDataActivityTypeShort("No");
                    mDevice.setDataActivityType("None");
                    break;
                case TelephonyManager.DATA_ACTIVITY_IN:
                    mDevice.setDataActivityTypeShort("In");
                    mDevice.setDataActivityType("In");
                    break;
                case TelephonyManager.DATA_ACTIVITY_OUT:
                    mDevice.setDataActivityTypeShort("Ou");
                    mDevice.setDataActivityType("Out");
                    break;
                case TelephonyManager.DATA_ACTIVITY_INOUT:
                    mDevice.setDataActivityTypeShort("IO");
                    mDevice.setDataActivityType("In-Out");
                    break;
                case TelephonyManager.DATA_ACTIVITY_DORMANT:
                    mDevice.setDataActivityTypeShort("Do");
                    mDevice.setDataActivityType("Dormant");
                    break;
            }
        }

        // In DB:   Di,Ct,Cd,Su
        public void onDataConnectionStateChanged(int state) {
            switch (state) {
                case TelephonyManager.DATA_DISCONNECTED:
                    mDevice.setDataState("Disconnected");
                    mDevice.setDataStateShort("Di");
                    break;
                case TelephonyManager.DATA_CONNECTING:
                    mDevice.setDataState("Connecting");
                    mDevice.setDataStateShort("Ct");
                    break;
                case TelephonyManager.DATA_CONNECTED:
                    mDevice.setDataState("Connected");
                    mDevice.setDataStateShort("Cd");
                    break;
                case TelephonyManager.DATA_SUSPENDED:
                    mDevice.setDataState("Suspended");
                    mDevice.setDataStateShort("Su");
                    break;
            }
        }

    };

    /**
     * Description:    Add entries to the "DBi_measure" DB table
     *
     * Issues:
     *                  [ ]
     *
     * Notes:           (a)
     *
     *
     * TODO:  Remove OLD notes below, once we have new ones relevant to our new table
     *
     *  From "locationinfo":
     *
     *      $ sqlite3.exe -header aimsicd.db 'select * from locationinfo;'
     *      _id|Lac|CellID|Net|Lat|Lng|Signal|Connection|Timestamp
     *      1|10401|6828xxx|10|54.67874392|25.28693531|24|[10401,6828320,126]No|Di|HSPA||2015-01-21 20:45:10
     *
     *  From "cellinfo":
     *
     *      $ sqlite3.exe -header aimsicd.db 'select * from cellinfo;'
     *      _id|Lac|CellID|Net|Lat|Lng|Signal|Mcc|Mnc|Accuracy|Speed|Direction|NetworkType|MeasurementTaken|OCID_SUBMITTED|Timestamp
     *      1|10401|6828xxx|10|54.67874392|25.28693531|24|246|2|69.0|0.0|0.0|HSPA|82964|0|2015-01-21 20:45:10
     *
     *  Issues:
     *
     */
    public void onLocationChanged(Location loc) {

        DeviceApi18.loadCellInfo(tm, mDevice);

        if (!mDevice.mCell.isValid()) {
            CellLocation cellLocation = tm.getCellLocation();
            if (cellLocation != null) {
                switch (mDevice.getPhoneID()) {

                    case TelephonyManager.PHONE_TYPE_NONE:
                    case TelephonyManager.PHONE_TYPE_SIP:
                    case TelephonyManager.PHONE_TYPE_GSM:
                        GsmCellLocation gsmCellLocation = (GsmCellLocation) cellLocation;
                        mDevice.mCell.setCID(gsmCellLocation.getCid()); // CID
                        mDevice.mCell.setLAC(gsmCellLocation.getLac()); // LAC
                        mDevice.mCell.setPSC(gsmCellLocation.getPsc()); // PSC
                        break;

                    case TelephonyManager.PHONE_TYPE_CDMA:
                        CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) cellLocation;
                        mDevice.mCell.setCID(cdmaCellLocation.getBaseStationId()); // BSID ??
                        mDevice.mCell.setLAC(cdmaCellLocation.getNetworkId());     // NID
                        mDevice.mCell.setSID(cdmaCellLocation.getSystemId());      // SID
                        mDevice.mCell.setMNC(cdmaCellLocation.getSystemId());      // MNC <== BUG!??

                        break;
                }
            }
        }

        if (loc != null &&  (Double.doubleToRawLongBits(loc.getLatitude()) != 0  &&  Double.doubleToRawLongBits(loc.getLongitude()) != 0)) {

            mDevice.mCell.setLon(loc.getLongitude());       // gpsd_lon
            mDevice.mCell.setLat(loc.getLatitude());        // gpsd_lat
            mDevice.mCell.setSpeed(loc.getSpeed());         // speed        // TODO: Remove, we're not using it!
            mDevice.mCell.setAccuracy(loc.getAccuracy());   // gpsd_accu
            mDevice.mCell.setBearing(loc.getBearing());     // -- [deg]??   // TODO: Remove, we're not using it!
            mDevice.setLastLocation(loc);                   //

            // Store last known location in preference
            SharedPreferences.Editor prefsEditor;
            prefsEditor = prefs.edit();
            prefsEditor.putString(context.getString(R.string.data_last_lat_lon),
                    String.valueOf(loc.getLatitude()) + ":" + String.valueOf(loc.getLongitude()));
            prefsEditor.apply();

            // This only logs a BTS if we have GPS lock
            // Test: ~~Is correct behaviour? We should consider logging all cells, even without GPS.~~
            //if (mTrackingCell) {
                // This also checks that the lac are cid are not in DB before inserting
                dbHelper.insertBTS(mDevice.mCell);
            //}
        }
    }

    /**
     * Cancel and remove the persistent notification
     */
    public void cancelNotification() {
        NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID);
    }

    /**
     *  Description:    Set or update the Detection/Status Notification
     *                  TODO: Need to add status HIGH (Orange) and SKULL (Black)
     *
     *  Issues:
     *      See:  https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/wiki/Status-Icons
     *      and:  https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/11#issuecomment-44670204
     *
     *  [ ] We need to standardize the "contentText" and "tickerText" format
     *
     *  [ ] From #91: https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/91
     *
     *      Problem:
     *          Having multiple notifications will cause an issue with
     *      notifications themselves AND tickerText.  It seems that the
     *      most recent notification raised would overwrite any previous,
     *      notification or tickerText.  This results in loss of information
     *      for any notification before the last one.
     *
     *      Possible Solution:
     *          Perhaps arranging a queue implementation to deal with text
     *      being passed into tickerText only when any previous text has
     *      been entirely displayed.
     *
     *  Dependencies:    Status.java, CellTracker.java, Icon.java ( + others?)
     *
     */
    private void setNotification() {
        String tickerText;
        String contentText = "Phone Type " + mDevice.getPhoneType();

        if (mTypeZeroSmsDetected) {
            getApplication().setCurrentStatus(Status.DANGER, mVibrateEnabled, mVibrateMinThreatLevel);
        } else if (mChangedLAC) {
            getApplication().setCurrentStatus(Status.MEDIUM, mVibrateEnabled, mVibrateMinThreatLevel);
            contentText = context.getString(R.string.hostile_service_area_changing_lac_detected);
        } else if (mCellIdNotInOpenDb) {
            getApplication().setCurrentStatus(Status.MEDIUM, mVibrateEnabled, mVibrateMinThreatLevel);
            contentText = context.getString(R.string.cell_id_doesnt_exist_in_db);
        } else if (mTrackingCell || mMonitoringCell) {
            getApplication().setCurrentStatus(Status.OK, mVibrateEnabled, mVibrateMinThreatLevel);
            if (mTrackingCell) {
                contentText = context.getString(R.string.cell_tracking_active);
            } 
            if (mMonitoringCell) {
                contentText = context.getString(R.string.cell_monitoring_active);
            } else {
                getApplication().setCurrentStatus(Status.IDLE, mVibrateEnabled, mVibrateMinThreatLevel);
            }
        } else {
            getApplication().setCurrentStatus(Status.IDLE, mVibrateEnabled, mVibrateMinThreatLevel);
        }


            Status status = getApplication().getStatus();
            switch (status) {
                case IDLE: // GRAY
                    contentText = context.getString(R.string.phone_type) + mDevice.getPhoneType();
                    tickerText = context.getResources().getString(R.string.app_name_short) + " " + context.getString(R.string.status_idle_description);
                    break;

                case OK: // GREEN
                    tickerText = context.getResources().getString(R.string.app_name_short) + " " + context.getString(R.string.status_ok_description);
                    break;

                case MEDIUM: // YELLOW
                    // Initialize tickerText as the app name string
                    // See multiple detection comments above.
                    tickerText = context.getResources().getString(R.string.app_name_short);
                    if (mChangedLAC) {
                        //Append changing LAC text
                        contentText = context.getString(R.string.hostile_service_area_changing_lac_detected);
                        tickerText += " - " + contentText;
                        // See #264 and ask He3556
                        //} else if (mNoNCList)  {
                        //    tickerText += " - BTS doesn't provide any neighbors!";
                        //    contentText = "CID: " + cellid + " is not providing a neighboring cell list!";

                    } else if (mCellIdNotInOpenDb) {
                        //Append Cell ID not existing in external db text
                        contentText = context.getString(R.string.cell_id_doesnt_exist_in_db);
                        tickerText += " - " + contentText;
                    }
                    break;

                case DANGER: // RED
                    tickerText = context.getResources().getString(R.string.app_name_short) + " - " + context.getString(R.string.alert_threat_detected); // Hmm, this is vague!
                    if (mTypeZeroSmsDetected) {
                        contentText = context.getString(R.string.alert_silent_sms_detected);
                    }

                    break;
                default:
                    tickerText = context.getResources().getString(R.string.main_app_name);
                    break;
            }

            // TODO: Explanation (see above)
            Intent notificationIntent = new Intent(context, MainActivity.class);
            notificationIntent.putExtra("silent_sms", mTypeZeroSmsDetected);
            notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_FROM_BACKGROUND);

            PendingIntent contentIntent = PendingIntent.getActivity(context, NOTIFICATION_ID, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

            String iconType = prefs.getString(context.getString(R.string.pref_ui_icons_key), "SENSE").toUpperCase(Locale.US);
            int iconResId = Icon.getIcon(Icon.Type.valueOf(iconType), status);
            Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), iconResId);

            //int color = context.getResources().getColor(status.getColor());
            int color = ContextCompat.getColor(context, status.getColor());

            Notification mBuilder = new NotificationCompat.Builder(context)
                    .setSmallIcon(R.drawable.tower48)
                    .setColor(color)
                    .setLargeIcon(largeIcon)
                    .setTicker(tickerText)
                    .setContentTitle(context.getString(R.string.status) + " " + context.getString(status.getName()))
                    .setContentInfo(context.getResources().getString(R.string.app_name_short))
                    .setContentText(contentText)
                    .setOngoing(true)
                    .setAutoCancel(false)
                    .setContentIntent(contentIntent)
                    .build();

        NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, mBuilder);

    }

    private AppAIMSICD getApplication() {
        return AppAIMSICD.getInstance();
    }

    /**
     * Vibrator helper method, will check current preferences (vibrator enabled, min threat level to vibrate)
     * and act appropriately
     */
    private void vibrate(int msec, Status threatLevel) {
        if (mVibrateEnabled && (threatLevel == null || threatLevel.ordinal() >= mVibrateMinThreatLevel)) {
            Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            v.vibrate(msec);
        }
    }

    //=================================================================================================
    // See issues: #6, #457, #489
    //
    // Summary:     We can detect femtocells by other means, using network data that we already have!
    //              The below code section was copied and modified with permission from
    //              Femtocatcher at:  https://github.com/iSECPartners/femtocatcher
    //
    //=================================================================================================

    // Removed!

    //=================================================================================================
    // END Femtocatcher code
    //=================================================================================================

    final PhoneStateListener phoneStatelistener = new PhoneStateListener() {
        private void handle() {
            handlePhoneStateChange();
        }
        @Override
        public void onServiceStateChanged(ServiceState serviceState) {
            handle();
        }
        @Override
        public void onDataConnectionStateChanged(int state) {
            handle();
        }
        @Override
        public void onDataConnectionStateChanged(int state, int networkType) {
            handle();
        }
        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            handle();
        }
        @Override
        public void onCellInfoChanged(List<CellInfo> cellInfo) {
            handle();
        }

        @Override
        public void onCellLocationChanged(CellLocation location) {
            handle();
        }

    };

    /**
     * Used in SmsDetector.java
     */
    public Cell getMonitorCell() {
        return mMonitorCell;
    }


} @XmlLayout(R.layout.activity_about)
public class AboutActivity extends InjectionAppCompatActivity {

    @InjectView(R.id.aimsicd_credits_link)
    private Button btncredits;

    @InjectView(R.id.aimsicd_version)
    private TextView versionNumber;

    @InjectView(R.id.build_number)
    private TextView buildNumberTextView;

    @InjectView(R.id.git_sha)
    private TextView gitShaTextView;


    @InjectView(R.id.textViewIdle)
    private TextView idleTextView;
    @InjectView(R.id.textViewOk)
    private TextView okTextView;
    @InjectView(R.id.textViewMedium)
    private TextView mediumTextView;
    @InjectView(R.id.textViewHigh)
    private TextView highTextView;
    @InjectView(R.id.textViewDanger)
    private TextView dangerTextView;
    @InjectView(R.id.textViewSkull)
    private TextView skullTextView;
    private boolean closeAfterFinish = true;
    private Context mContext = this;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        versionNumber.setText(getString(R.string.app_version, BuildConfig.VERSION_NAME));

        String buildNumber = BuildConfig.BUILD_NUMBER;
        //noinspection ConstantConditions
        if (buildNumber == null) {
            buildNumber = getString(R.string.n_a);
        }
        buildNumberTextView.setText(getString(R.string.buildnumber, buildNumber));

        gitShaTextView.setText(getString(R.string.git_sha, BuildConfig.GIT_SHA));

        //GitHub WIKI Link
        View tv = findViewById(R.id.aimsicd_wiki_link);
        setLink(tv, R.string.aimsicd_wiki_link);

        //GitHub Website Link
        tv = findViewById(R.id.aimsicd_github_link);
        setLink(tv, R.string.aimsicd_github_link);

        //Disclaimer Link
        tv = findViewById(R.id.aimsicd_disclaimer_link);
        setLink(tv, R.string.disclaimer_link);

        //GitHub Contribution Link
        tv = findViewById(R.id.aimsicd_contribute_link);
        setLink(tv, R.string.aimsicd_contribute_link);

        //WIP Release Link
        tv = findViewById(R.id.aimsicd_release_link);
        setLink(tv, R.string.aimsicd_release_link);

        //Changelog Link
        tv = findViewById(R.id.aimsicd_changelog_link);
        setLink(tv, R.string.aimsicd_changelog_link);

        //License Link
        tv = findViewById(R.id.aimsicd_license_link);
        setLink(tv, R.string.aimsicd_license_link);

        idleTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.IDLE);
            }
        });

        okTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.OK);
            }
        });

        mediumTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.MEDIUM);
            }
        });

        highTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.HIGH);
            }
        });

        dangerTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.DANGER);
            }
        });

        skullTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showInfoDialog(Status.SKULL);
            }
        });

        btncredits.setOnClickListener(new View.OnClickListener() {
            AlertDialog mCreditsDialog;
            @Override
            public void onClick(View view) {
                final TextView mCreditsTitle = new TextView(getApplicationContext());
                final TextView mCreditsText = new TextView(mContext);
                final ScrollView mScrollView = new ScrollView(getApplicationContext());
                mCreditsText.setText(R.string.about_credits_content);
                mCreditsText.setTextSize(18);
                mCreditsText.setTypeface(Typeface.DEFAULT_BOLD);
                mCreditsTitle.setText(R.string.about_credits);
                mCreditsTitle.setTypeface(Typeface.DEFAULT_BOLD);
                mCreditsTitle.setTextSize(20);
                mCreditsTitle.setGravity(Gravity.CENTER_HORIZONTAL);
                mScrollView.addView(mCreditsText);
                Animation scrollTo = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.credits_dialog_scroll);
                mCreditsText.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        closeAfterFinish = false;
                        mCreditsText.clearAnimation();
                    }
                });
                scrollTo.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (closeAfterFinish) {
                            mCreditsDialog.cancel();
                        }
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });
                mCreditsText.setGravity(Gravity.CENTER_HORIZONTAL);
                mCreditsText.setMovementMethod(LinkMovementMethod.getInstance());
                scrollTo.setDuration(countLines(getResources().getString(R.string.about_credits_content)) * 500);
                mCreditsText.startAnimation(scrollTo);
                closeAfterFinish = true;
                mCreditsDialog = new AlertDialog.Builder(AboutActivity.this)
                        .setCustomTitle(mCreditsTitle)
                        .setPositiveButton(android.R.string.ok, null)
                        .setView(mScrollView)
                        .show();
            }
        });
    }

    private void setLink(View b, final int link) {
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Intent.ACTION_VIEW,
                        Uri.parse(getResources().getString(link)));
                startActivity(intent);
            }
        });
    }

    private void showInfoDialog(Status status) {
        new AlertDialog.Builder(this)
                .setIcon(status.getIcon())
                .setTitle(getString(R.string.status) + "\t" + getString(status.getName()))
                .setMessage(status.getDescription())
                .setPositiveButton(android.R.string.ok, null)
                .show();
    }

    public enum Status {
        IDLE(
                R.drawable.sense_idle,
                R.string.status_idle,
                R.string.status_idle_detail_info
        ),
        OK(
                R.drawable.sense_ok,
                R.string.status_ok,
                R.string.status_ok_detail_info
        ),
        MEDIUM(
                R.drawable.sense_medium,
                R.string.status_medium,
                R.string.status_medium_detail_info
        ),
        HIGH(
                R.drawable.sense_high,
                R.string.status_high,
                R.string.status_high_detail_info
        ),
        DANGER(
                R.drawable.sense_danger,
                R.string.status_danger,
                R.string.status_danger_detail_info
        ),
        SKULL(
                R.drawable.sense_skull,
                R.string.status_skull,
                R.string.status_skull_detail_info
        );

        @DrawableRes
        private int icon;

        @StringRes
        private int name;

        @StringRes
        private int description;

        Status(@DrawableRes int icon,
               @StringRes int name,
               @StringRes int description) {
            this.icon = icon;
            this.name = name;
            this.description = description;
        }

        @DrawableRes
        public int getIcon() {
            return icon;
        }

        @StringRes
        public int getName() {
            return name;
        }

        @StringRes
        public int getDescription() {
            return description;
        }
    }
    private  int countLines(String mString) {
        String[] lines = mString.split("\r\n|\r|\n");
        return  lines.length;
    }


} public abstract class BaseActivity extends InjectionAppCompatActivity {

    public static final String TAG = "AICDL";
    public static final String mTAG = "BaseActivity:";

    // Triggered when GUI is opened
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, mTAG + "StatusWatcher starting watching");
        LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter("StatusChange"));
        updateIcon(this);
    }

    // Message reciever that handles icon update when status changes
    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, mTAG + "StatusWatcher received status change to " + ((AppAIMSICD) getApplication()).getStatus().name() + ", updating icon");
            updateIcon(context);
        }
    };

    private void updateIcon(Context context) {
        SharedPreferences prefs = context.getSharedPreferences(AimsicdService.SHARED_PREFERENCES_BASENAME, 0);
        //final String iconType = prefs.getString(context.getString(R.string.pref_ui_icons_key), "SENSE").toUpperCase();
        final String iconTypeString = prefs.getString(context.getString(R.string.pref_ui_icons_key), "SENSE").toUpperCase(Locale.US);
        final Icon.Type iconType = Icon.Type.valueOf(iconTypeString);

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //if (getActionBar() != null) {
                //    getActionBar().setIcon(Icon.getIcon(Icon.Type.valueOf(iconType), ((AppAIMSICD) getApplication()).getStatus()));
                if(getSupportActionBar() != null) {
                    Status status = ((AppAIMSICD) getApplication()).getStatus();
                    int newIcon = Icon.getIcon(iconType, status);
                    getSupportActionBar().setIcon(newIcon);

                }
            }
        });
    }

    // Triggered when GUI is closed/put to background
    @Override
    protected void onPause() {
        super.onPause();
       Log.d(TAG, mTAG + "StatusWatcher stopped watching");
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
    }
} @XmlLayout(R.layout.activity_debug_logs)
@XmlMenu(R.menu.activity_debug_logs)
public class DebugLogs extends BaseActivity {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";

    private LogUpdaterThread logUpdater = null;
    private boolean updateLogs = true;
    private boolean isRadioLogs = true; // Including this, should be a toggle.

    @InjectView(R.id.debug_log_view)
    private TextView logView;

    @InjectView(R.id.btnClear)
    private Button btnClear;

    @InjectView(R.id.btnCopy)
    private Button btnCopy;

    @InjectView(zz.aimsicd.lite.R.id.btnStopLogs)
    private Button btnStop;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Show the Up button in the action bar.
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                logView.setFocusable(false);
            }
        });

        btnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    clearLogs();
                } catch (IOException e) {
                   Log.e(TAG, mTAG + "Error clearing logs", e);
                }
            }
        });

        btnCopy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                ClipData cd = ClipData.newPlainText("log", logView.getText());
                clipboard.setPrimaryClip(cd);
                Helpers.msgShort(DebugLogs.this, getString(R.string.msg_copied_to_clipboard));
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (updateLogs) {
                    updateLogs = false;
                    btnStop.setText(getString(R.string.btn_start_logs));
                } else {
                    startLogging();
                }
            }
        });

        /*
        // logcat radio buffer toggle on/off
        btnRadio.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (isRadioLogs) {
                    isRadioLogs = false;
                    btnRadio.setText(getString(R.string.btn_radio_logs));
                } else {
                    isRadioLogs = true;
                    btnRadio.setText(getString(R.string.btn_main_logs));
                }
            }
        });
        */

    }

    @Override
    protected void onPause() {
        updateLogs = false; // exit the log updater
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        startLogging();
    }

    private void startLogging() {
        updateLogs = true;

        logUpdater = new LogUpdaterThread();
        logUpdater.start();

        btnStop.setText(getString(R.string.btn_stop_logs));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case zz.aimsicd.lite.R.id.action_send_logs:
                sendEmail();
                return true;

            case android.R.id.home:
                // This ID represents the Home or Up button. In the case of this
                // activity, the Up button is shown. Use NavUtils to allow users
                // to navigate up one level in the application structure. For
                // more details, see the Navigation pattern on Android Design:
                //
                // http://developer.android.com/design/patterns/navigation.html#up-vs-back
                //
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    public void sendEmail() {
        new Thread() {
            @Override
            public void run() {
                // Send Error Log
                try {
                    String helpUs = getString(R.string.describe_the_problem_you_had);
                    String log = helpUs + "\n\n" + "GETPROP:" + "\n\n" + getProp() +
                                          "\n\n" + "LOGCAT:" + "\n\n" + getLogs() + "\n\n" + helpUs;

                    // show a share intent
                    Intent intent = new Intent(Intent.ACTION_SEND);
                    intent.setType("text/html");
                    // E-Mail address will ONLY be handed out when a DEVELOPER asked for the logs!
                    intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"See GitHub Issues first!"});
                    intent.putExtra(Intent.EXTRA_SUBJECT, "AIMSICD Error Log");
                    intent.putExtra(Intent.EXTRA_TEXT, log);
                    startActivity(Intent.createChooser(intent, "Send Error Log"));
                } catch (IOException e) {
                   Log.w(TAG, mTAG + "Error reading logs", e);
                }
            }
        }.start();
    }

    /**
     * Read getprop and return the sorted result as a string
     *
     * TODO: Need a way to sort properties for easy reading
     *
     * @return
     * @throws IOException
     */
    public String getProp() throws IOException {
        return runProcess("/system/bin/getprop");
    }

    /**
     *  Description:    Read logcat and return as a string
     *
     *  Notes:
     *
     *  1) " *:V" makes log very spammy due to verbose OemRilRequestRaw debug output (AIMSICD_Helpers).
     *          ==> Now disabled!
     *  2) Need to silent some spammy Samsung Galaxy's:     " AbsListView:S PackageInfo:S"
     *  3) Need to silent some Qualcomm QMI:                " RILQ:S"
     *  4) Need to silent some Qualcomm GPS:                " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S"
     *  5) "-d" is not necessary when using "-t".
     *  6) Need to silent some spammy HTC's:                "QC-QMI:S AudioPolicyManager:S"
     *  7) Need to silent some spammy XPrivacy items:       "XPrivacy/XRuntime:S Xposed:S"
     *  8) Need to silent even more XPrivacy items:         "XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S"
     *
     */
    private String getLogs() throws IOException {
        return runProcess(
            "logcat -t 500 -v brief -b main" +
                    (isRadioLogs ? " -b radio RILQ:S" : "") +
                    " AbsListView:S PackageInfo:S" +
                    " LocSvc_eng:S LocSvc_adapter:S LocSvc_afw:S" +
                    " QC-QMI:S AudioPolicyManager:S" +
                    " XPrivacy/XRuntime:S Xposed:S" +
                    " XPrivacy/XTelephonyManager:S XPrivacy/XLocationManager:S XPrivacy/XPackageManager:S" + " *:D"
        );
    }

    /**
     * Run a shell command and return the results
     */
    private String runProcess(String command) throws IOException {
        return runProcess(new String[]{command});
    }

    /**
     * Run a shell command and return the results
     *
     * @param command
     * @return
     * @throws IOException
     */
    private String runProcess(String[] command) throws IOException {
        Process process = null;
        if (command.length == 1) {
            process = Runtime.getRuntime().exec(command[0]);
        } else {
            Runtime.getRuntime().exec(command);
        }

        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

        StringBuilder log = new StringBuilder();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            log.append(line);
            log.append("\n");
        }
        bufferedReader.close();
        return log.toString();
    }

    /**
     * Clear logcat
     * @return
     * @throws IOException
     */
    private void clearLogs() throws IOException {
        new Thread() {
            @Override
            public void run() {
                try {
                    Runtime.getRuntime().exec("logcat -c -b main -b system -b radio -b events");
                } catch (IOException e) {
                   Log.e(TAG, mTAG + "Error clearing logs", e);
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        logView.setText("");
                    }
                });
            }
        }.start();
    }

    class LogUpdaterThread extends Thread {
        @Override
        public void run() {
            while (updateLogs) {
                try {
                    final String logs = getLogs();
                    if (!logs.equals(logView.getText().toString())) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // update log display
                                logView.setText(logs);

                                // scroll to the bottom of the log display
                                final ScrollView scroll = ((ScrollView) logView.getParent());
                                scroll.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        scroll.fullScroll(View.FOCUS_DOWN);
                                    }
                                });
                            }
                        });
                    }
                } catch (IOException e) {
                   Log.w(TAG, mTAG + "Error updating logs", e);
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                   Log.w(TAG, mTAG + "Thread was interrupted", e);
                }
            }
        }
    }
} public class SettingsActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        loadFragment();
    }

    private void loadFragment() {
        SettingsFragment settingsFragment = new SettingsFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(android.R.id.content, settingsFragment);
        fragmentTransaction.commit();
    }
} public class DrawerMenuActivityConfiguration {

    private int mMainLayout;
    private int mDrawerLayoutId;
    private int mLeftDrawerId;
    private int[] mActionMenuItemsToHideWhenDrawerOpen;
    private List<NavDrawerItem> mNavItems;
    private BaseAdapter mBaseAdapter;

    private DrawerMenuActivityConfiguration(Builder pBuilder) {
        mMainLayout = pBuilder.mMainLayout;
        mDrawerLayoutId = pBuilder.mDrawerLayoutId;
        mLeftDrawerId = pBuilder.mLeftDrawerId;
        mActionMenuItemsToHideWhenDrawerOpen = pBuilder.mActionMenuItemsToHideWhenDrawerOpen;
        mNavItems = pBuilder.mNavItems;
        mBaseAdapter = pBuilder.mBaseAdapter;
    }

    public int getMainLayout() {
        return mMainLayout;
    }

    public int getDrawerLayoutId() {
        return mDrawerLayoutId;
    }

    public int getLeftDrawerId() {
        return mLeftDrawerId;
    }

    public int[] getActionMenuItemsToHideWhenDrawerOpen() {
        return mActionMenuItemsToHideWhenDrawerOpen;
    }

    public List<NavDrawerItem> getNavItems() {
        return mNavItems;
    }

    public BaseAdapter getBaseAdapter() {
        return mBaseAdapter;
    }

    public static class Builder {

        private final Context mContext;
        private int mMainLayout;
        private int mDrawerLayoutId;
        private int mLeftDrawerId;
        private int[] mActionMenuItemsToHideWhenDrawerOpen;
        private List<NavDrawerItem> mNavItems;
        private int mDrawerOpenDesc;
        private int mDrawerCloseDesc;
        private BaseAdapter mBaseAdapter;

        public Builder(Context pContext) {
            mContext = pContext;
        }

        public Builder mainLayout(int pMainLayout) {
            mMainLayout = pMainLayout;
            return this;
        }

        public Builder drawerLayoutId(int pDrawerLayoutId) {
            mDrawerLayoutId = pDrawerLayoutId;
            return this;
        }

        public Builder leftDrawerId(int pLeftDrawerId) {
            mLeftDrawerId = pLeftDrawerId;
            return this;
        }

        public Builder drawerOpenDesc(int pDrawerOpenDesc) {
            mDrawerOpenDesc = pDrawerOpenDesc;
            return this;
        }

        public Builder drawerCloseDesc(int pDrawerCloseDesc) {
            mDrawerCloseDesc = pDrawerCloseDesc;
            return this;
        }

        public DrawerMenuActivityConfiguration build() {

            List<NavDrawerItem> menu = new ArrayList<>();

            //Section Main
            menu.add(DrawerMenuSection.create(DrawerMenu.ID.SECTION_MAIN, mContext.getString(R.string.main)));
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.MAIN.PHONE_SIM_DETAILS, mContext.getString(R.string.device_info), R.drawable.ic_action_phone, true));           // Phone/SIM Details
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.MAIN.CURRENT_TREAT_LEVEL, mContext.getString(R.string.cell_info_title), R.drawable.cell_tower, true));            // Cell Information (Neighboring cells etc)
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.MAIN.DB_VIEWER, mContext.getString(R.string.db_viewer), R.drawable.ic_action_storage, true));           // Database Viewer
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.MAIN.ANTENNA_MAP_VIEW, mContext.getString(R.string.map_view), R.drawable.ic_action_map, false));               // Antenna Map Viewer
            //menu.add(DrawerMenuItem.create(DrawerMenu.ID.MAIN.AT_COMMAND_INTERFACE, mContext.getString(R.string.at_command_title), R.drawable.ic_action_computer, true));   // AT Command Interface

            //Section Settings
            menu.add(DrawerMenuSection.create(DrawerMenu.ID.SECTION_DATABASE_SETTINGS, mContext.getString(R.string.database_settings)));
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.DATABASE_SETTINGS.BACKUP_DB, mContext.getString(R.string.backup_database), R.drawable.ic_action_import_export, false));   // Backup Database
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.DATABASE_SETTINGS.RESTORE_DB, mContext.getString(R.string.restore_database), R.drawable.ic_action_import_export, false));  // Restore Database
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.DATABASE_SETTINGS.RESET_DB, mContext.getString(R.string.clear_database), R.drawable.ic_action_delete_database, false));  // Reset Database

            //Section Application
            menu.add(DrawerMenuSection.create(DrawerMenu.ID.SECTION_APPLICATION, mContext.getString(R.string.application)));
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.APPLICATION.DOWNLOAD_LOCAL_BTS_DATA, mContext.getString(R.string.get_opencellid), R.drawable.stat_sys_download_anim0, false));   // "Download Local BTS data"
            //menu.add(DrawerMenuItem.create(DrawerMenu.ID.APPLICATION.UPLOAD_LOCAL_BTS_DATA, mContext.getString(R.string.upload_bts), R.drawable.stat_sys_upload_anim0, false));      // "Upload Local BTS data"
            menu.add(DrawerMenuItem.create(DrawerMenu.ID.APPLICATION.QUIT, mContext.getString(R.string.quit), R.drawable.ic_action_remove, false));                // Quit
            mNavItems = menu;

            if (mMainLayout == 0) {
                this.mainLayout(R.layout.activity_main);
            }

            if (mDrawerLayoutId == 0) {
                this.drawerLayoutId(R.id.drawer_layout);
            }

            if (mLeftDrawerId == 0) {
                this.leftDrawerId(R.id.left_drawer);
            }

            if (mDrawerOpenDesc == 0) {
                this.drawerOpenDesc(R.string.drawer_open);
            }

            if (mDrawerCloseDesc == 0) {
                this.drawerCloseDesc(R.string.drawer_close);
            }

            if (mBaseAdapter == null) {
                mBaseAdapter = new DrawerMenuAdapter(mContext, R.layout.drawer_item, menu);
            }

            return new DrawerMenuActivityConfiguration(this);
        }

    }
} public interface NavDrawerItem {
    int getId();
    String getLabel();
    void setLabel(String label);
    void setmIconId(int icon);
    int getType();
    boolean isEnabled();
    boolean updateActionBarTitle();
} @XmlLayout(R.layout.activity_at_command)
public class AtCommandFragment extends InjectionFragment {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";

    //Return value constants
    private static final int SERIAL_INIT_OK = 100;
    private static final int SERIAL_INIT_ERROR = 101;
    private static final int ROOT_UNAVAILABLE = 102;
    private static final int BUSYBOX_UNAVAILABLE = 103;
    private static final List<String> mSerialDevices = new ArrayList<>();

    private String mSerialDevice;
    private int mTimeout;

    @InjectView(R.id.atcommandView)
    private RelativeLayout mAtCommandLayout;

    @InjectView(R.id.at_command_error)
    private TextView mAtCommandError;

    @InjectView(R.id.serial_device)
    private TextView mSerialDeviceDisplay;

    @InjectView(R.id.response)
    private TextView mAtResponse;

    @InjectView(zz.aimsicd.lite.R.id.at_command)
    private EditText mAtCommand;

    @InjectView(R.id.serial_device_spinner)
    private Spinner mSerialDeviceSpinner;

    @InjectView(zz.aimsicd.lite.R.id.serial_device_spinner_title)
    private TextView mSerialDeviceSpinnerLabel;

    private AtCommandTerminal mCommandTerminal;

    @InjectView(R.id.timeout_spinner)
    private Spinner timeoutSpinner;

    @InjectView(zz.aimsicd.lite.R.id.execute)
    private Button atCommandExecute;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        atCommandExecute.setOnClickListener(new btnClick());
        mSerialDeviceSpinner.setOnItemSelectedListener(new spinnerListener());
        timeoutSpinner.setOnItemSelectedListener(new timeoutSpinnerListener());
        timeoutSpinner.setSelection(1);
        mTimeout = 5000;
    }

    private class timeoutSpinnerListener implements AdapterView.OnItemSelectedListener {

        @Override
        public void onItemSelected(AdapterView<?> parentView, View selectedItemView,
                int position, long id) {
            switch (position) {
                // Don't forget to also change the arrays.xml
                case 0: //2 seconds
                    mTimeout = 2000;
                    break;
                case 1: //5 seconds
                    mTimeout = 5000;
                    break;
                case 2: //10 seconds
                    mTimeout = 10000;
                    break;
                case 3: //20 seconds
                    mTimeout = 20000;
                    break;
                case 4: //30 seconds
                    mTimeout = 30000;
                    break;
                case 5: // No timeout, when watching output...
                    mTimeout = 600000; // Well, ok, 10 min
                    break;
                default:
                    mTimeout = 5000;
            }
        }

        @Override
        public void onNothingSelected(AdapterView<?> parentView) {
        }
    }

    private class spinnerListener implements AdapterView.OnItemSelectedListener {

        @Override
        public void onItemSelected(AdapterView<?> parentView, View selectedItemView,
                int position, long id) {
            mSerialDevice = String.valueOf(mSerialDeviceSpinner.getSelectedItem());
            mSerialDeviceDisplay.setText(mSerialDevice);
            setSerialDevice();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parentView) {

        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mCommandTerminal != null) {
            mCommandTerminal.dispose();
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        int serialDevice = initSerialDevice();

        switch (serialDevice) {
            case SERIAL_INIT_OK:
                mAtCommandLayout.setVisibility(View.VISIBLE);
                break;
            case ROOT_UNAVAILABLE:
                mAtCommandError.setText(zz.aimsicd.lite.R.string.unable_to_acquire_root_access);
                break;
            case BUSYBOX_UNAVAILABLE:
                mAtCommandError.setText(zz.aimsicd.lite.R.string.unable_to_detect_busybox);
                break;
            case SERIAL_INIT_ERROR:
                mAtCommandError.setText(R.string.unknown_error_trying_to_acquire_serial_device);
                break;
            default:
                mAtCommandError.setText(R.string.unknown_error_initialising_at_command_injector);
                break;
        }

    }

    private class btnClick implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            if (mAtCommand.getText() != null) {
                String command = mAtCommand.getText().toString();
                Log.i(TAG, mTAG + "AT Command Detected: " + command);
                executeAT();
            }
        }
    }

    /**
     *  Description:    This is looking for possible serial devices that may be used for ATCoP.
     *
     *  Issues:         This is generally not working since it is very HW and SW dependent.
     *
     *
     * @return
     */
    private int initSerialDevice() {

        /*
         * NOTE:
         *
         *      Because of how RootShell is being used the handler has to be disabled.
         *
         *      With the handler disabled absolutely NO UI work can be done in the callback methods
         *      since they will be called on a separate thread.
         *
         *      To work around this, either:
         *
         *      a) Execute all Shell commands in a thread, such as AsyncTask
         *          OR
         *      b) Stop using commandWait (which is a no no...you should never sleep on the
         *         main UI thread) and implement the callback commandFinished/commandTerminated
         *         to determine when to continue on.
         *
         */
        RootShell.handlerEnabled = false;

        // Check for root access
        boolean root = RootShell.isAccessGiven();
        if (!root) {
            return ROOT_UNAVAILABLE;
        }

        // Check if Busybox is installed
        boolean busybox = RootShell.isBusyboxAvailable();
        if (!busybox) {
            return BUSYBOX_UNAVAILABLE;
        }

        try {
            mAtResponse.setText(R.string.at_command_response_looking);
            mSerialDevices.clear();

            // THIS IS A BAD IDEA       TODO: Consider removing
            // Use RIL Serial Device details from the System Property
            try {
                String rilDevice = Helpers.getSystemProp(getActivity(), "rild.libargs", "UNKNOWN");
                mSerialDevice = ("UNKNOWN".equals(rilDevice) ? rilDevice : rilDevice.substring(3));

                if (!"UNKNOWN".equals(mSerialDevice)) {
                    mSerialDevices.add(mSerialDevice);
                }
            } catch (StringIndexOutOfBoundsException e) {
               Log.w(TAG, mTAG + e.getMessage());
                // ignore, move on
            }

            //==================================================================
            // WARNING:  Scraping commands can be masked by aliases in: mkshrc
            //           and even hardcoded in the sh binary or elsewhere.
            //           To get unaliased versions, use: "\\<command>"
            //==================================================================

            for (File file : new File("/dev").listFiles()) {
                String name = file.getName();
                boolean add = false;
                // QC: /dev/smd[0-7]
                if (name.matches("^smd.$")) {
                    add = true;
                } else if ("radio".equals(name)) {
                    // MTK: /dev/radio/*atci*
                    for (File subfile : file.listFiles()) {
                        String subname = subfile.getName();
                        if (subname.contains("atci")) {
                            add = true;
                            file = subfile;
                        }
                    }
                }

                if (add) {
                    mSerialDevices.add(file.getAbsolutePath());
                    mAtResponse.append(getString(R.string.at_command_response_found) + file.getAbsolutePath() + "\n");
                }
            }

            // Now try XMM/XGOLD modem config
            File xgold = new File("/system/etc/ril_xgold_radio.cfg");
            if (xgold.exists() && xgold.isFile()) {
                Command cmd = new Command(1, "\\cat /system/etc/ril_xgold_radio.cfg | "
                                            + "\\grep -E \"atport*|dataport*\"") {

                    @Override
                    public void commandOutput(int id, String line) {
                        if (id == 0) {
                            if (!line.trim().isEmpty() && line.contains("/dev/")) {
                                int place = line.indexOf("=") + 1;
                                mSerialDevices.add(line.substring(place, line.length() - 1));
                                mAtResponse.append(getString(R.string.at_command_response_found) + line.substring(place, line.length() - 1) + "\n");
                            }
                        }
                        super.commandOutput(id, line);
                    }
                };

                Shell shell = RootShell.getShell(true);
                shell.add(cmd);
                commandWait(shell, cmd);
            }

        } catch (Exception e) {
           Log.e(TAG, mTAG + "InitSerialDevice ", e);
        }

        if (!mSerialDevices.isEmpty()) {
            String[] entries = new String[mSerialDevices.size()];
            entries = mSerialDevices.toArray(entries);
            ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getActivity(),
                    android.R.layout.simple_spinner_item, entries);
            mSerialDeviceSpinner.setAdapter(spinnerAdapter);
            mSerialDeviceSpinner.setVisibility(View.VISIBLE);
            mSerialDeviceSpinnerLabel.setVisibility(View.VISIBLE);
        }

        mAtResponse.append(getString(R.string.at_command_response_setup_complete));
        mAtResponse.setVisibility(View.VISIBLE);

        return SERIAL_INIT_OK;
    }

    private void setSerialDevice() {
        if (mCommandTerminal != null) {
            mCommandTerminal.dispose();
            mCommandTerminal = null;
        }
    }

    private AtCommandTerminal getSerialDevice() {
        if (mCommandTerminal == null) {
            try {
                mCommandTerminal = new TtyPrivFile(mSerialDevice);
                return mCommandTerminal;
            } catch (IOException e) {
                mAtResponse.append(e.toString());
            }
        } else {
            return mCommandTerminal;
        }
        return null;
    }

    private void executeAT() {
        // It seem that MTK devices doesn't need "\r" but QC devices do.
        // We need a device-type check here, perhaps: gsm.version.ril-impl.
        Editable cmd = mAtCommand.getText();
        if (cmd != null && cmd.length() != 0) {
           Log.d(TAG, mTAG + "ExecuteAT: attempting to send: " + cmd.toString());

            if (getSerialDevice() != null) {
                mCommandTerminal.send(cmd.toString(), new Handler(Looper.getMainLooper()) {
                    @Override
                    public void handleMessage(Message message) {
                        if (message.obj instanceof List) {
                            // Fixme: unchecked cast
                            List<String> lines = ((List<String>) message.obj);
                            StringBuffer response = new StringBuffer();
                            for (String line : lines) {
                                response.append(line);
                                response.append('\n');
                            }
                            if (response.length() != 0) {
                                mAtResponse.append(response);
                            }

                        } else if (message.obj instanceof IOException) {
                            mAtResponse.append("IOException: " + ((IOException) message.obj).getMessage() + "\n");
                        }
                    }
                }.obtainMessage());
            }
        }

    }

    /**
     * This below method is part of the RootTools Project: https://github.com/Stericson/RootTools
     * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
     *
     * Slightly modified commandWait method as found in RootToolsInternalMethods.java to utilise
     * the user selected timeout value.
     *
     */
    private void commandWait(Shell shell, Command cmd) throws Exception {
        while (!cmd.isFinished()) {
            synchronized (cmd) {
                try {
                    if (!cmd.isFinished()) {
                        cmd.wait(mTimeout);
                    }
                } catch (InterruptedException e) {
                   Log.e(TAG, mTAG + e.getMessage());
                }
            }
            if (!cmd.isExecuting() && !cmd.isFinished()) {
                Exception e = new Exception();

                if (!shell.isExecuting && !shell.isReading) {
                   Log.w(TAG, mTAG + "Waiting for a command to be executed in a shell that is not executing and not reading! \n\n Command: " + cmd.getCommand());
                    e.setStackTrace(Thread.currentThread().getStackTrace());
                   Log.e(TAG, mTAG + e.getMessage(), e);

                } else if (shell.isExecuting && !shell.isReading) {
                   Log.e(TAG, mTAG + "Waiting for a command to be executed in a shell that is executing but not reading! \n\n Command: " + cmd.getCommand());
                    e.setStackTrace(Thread.currentThread().getStackTrace());
                   Log.e(TAG, mTAG + e.getMessage(), e);

                } else {
                   Log.e(TAG, mTAG + "Waiting for a command to be executed in a shell that is not reading! \n\n Command: " + cmd.getCommand());
                    e.setStackTrace(Thread.currentThread().getStackTrace());
                   Log.e(TAG, mTAG + e.getMessage(), e);
                }
            }
        }
    }
} @XmlLayout(zz.aimsicd.lite.R.layout.fragment_cell_info)
public class CellInfoFragment extends InjectionFragment implements SwipeRefreshLayout.OnRefreshListener {

    public static final int STOCK_REQUEST = 1;
    public static final int SAMSUNG_MULTIRIL_REQUEST = 2;

    private AimsicdService mAimsicdService;
    private RilExecutor rilExecutor;
    private boolean mBound;
    private Context mContext;
    private final Handler timerHandler = new Handler();

    private List<Cell> neighboringCells;

    @InjectView(zz.aimsicd.lite.R.id.list_view)
    private ListView lv;

    @InjectView(zz.aimsicd.lite.R.id.swipeRefreshLayout)
    private SwipeRefreshLayout swipeRefreshLayout;

    private BaseInflaterAdapter<CardItemData> mBaseInflaterAdapter;
    private CellInfoAdapter mCellInfoAdapter;

    private final Runnable timerRunnable = new Runnable() {


        @Override
        public void run() {
            updateUI();
            timerHandler.postDelayed(this, CellTracker.REFRESH_RATE);
        }
    };

    @Override
    public void onPause() {
        super.onPause();
        timerHandler.removeCallbacks(timerRunnable);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!mBound) {
            // Bind to LocalService
            Intent intent = new Intent(mContext, AimsicdService.class);
            mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }

        // Refresh display if preference (pref_refresh_values) is not set to manual (0)
        // For automatic it is "1" and defined in:
        //    CellTracker.java :: onSharedPreferenceChanged()
        if (CellTracker.REFRESH_RATE != 0) {
            timerHandler.postDelayed(timerRunnable, 0);
            Helpers.msgShort(mContext, mContext.getString(zz.aimsicd.lite.R.string.refreshing_every) + " " +
                    TimeUnit.MILLISECONDS.toSeconds(CellTracker.REFRESH_RATE) + " " + mContext.getString(zz.aimsicd.lite.R.string.seconds));
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mBaseInflaterAdapter = new BaseInflaterAdapter<>(new CellCardInflater());
        mCellInfoAdapter = new CellInfoAdapter(mBaseInflaterAdapter, new CellInfoOverviewData());
        lv.setAdapter(mCellInfoAdapter);

        mContext = getActivity().getBaseContext();
        // Bind to LocalService
        Intent intent = new Intent(mContext, AimsicdService.class);
        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        swipeRefreshLayout.setOnRefreshListener(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Unbind from the service
        if (mBound) {
            mContext.unbindService(mConnection);
            mBound = false;
        }
        timerHandler.removeCallbacks(timerRunnable);
    }

    /**
     * Service Connection to bind the activity to the service
     */
    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            mAimsicdService = ((AimsicdService.AimscidBinder) service).getService();
            rilExecutor = mAimsicdService.getRilExecutor();
            mBound = true;
            updateUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

    @Override
    public void onRefresh() {
        updateUI();
        swipeRefreshLayout.setRefreshing(false);
    }


    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            updateUI();
        }
    }

    private void updateUI() {
        if (mBound && rilExecutor.mMultiRilCompatible) {
                new CellAsyncTask().execute(SAMSUNG_MULTIRIL_REQUEST);
            } else {
            new CellAsyncTask().execute(STOCK_REQUEST);
        }
    }

    void updateCipheringIndicator() {
        final List<String> list = rilExecutor.getCipheringInfo();
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (list != null) {
                    //mCipheringIndicatorLabel.setVisibility(View.VISIBLE);
                    //mCipheringIndicator.setVisibility(View.VISIBLE);
                    //mCipheringIndicator.setText(TextUtils.join("\n", list));
                    CellInfoOverviewData overviewData = new CellInfoOverviewData();
                    overviewData.mCipheringIndicatorLabelVisibility = View.VISIBLE;
                    overviewData.mCipheringIndicatorVisibility = View.VISIBLE;
                    overviewData.mCipheringIndicator = TextUtils.join("\n", list);
                    if (mCellInfoAdapter != null) {
                        mCellInfoAdapter.updateCellInfoOverview(overviewData);
                    }
                }
            }
        });
    }

    boolean getStockNeighbouringCells() {
        if (mBound) {
            neighboringCells = mAimsicdService.getCellTracker().updateNeighbouringCells();
            return neighboringCells.size() > 0;
        }

        return false;
    }

    void updateStockNeighbouringCells() {
        //mNeighbouringTotal.setText(String.valueOf(neighboringCells.size()));
        CellInfoOverviewData overviewData = new CellInfoOverviewData();
        overviewData.mNeighboringTotal = neighboringCells.size();
        mBaseInflaterAdapter.clear(false);

        if (neighboringCells.size() != 0) {

            //BaseInflaterAdapter<CardItemData> adapter = new BaseInflaterAdapter<>(new CellCardInflater());
            int i = 1;
            int total = neighboringCells.size();
            for (Cell cell : neighboringCells) {
                CardItemData data = new CardItemData(cell, i++ + " / " + total);
                //adapter.addItem(data, false);
                mBaseInflaterAdapter.addItem(data, false);
            }
            overviewData.mNeighboringCellsVisibility = View.GONE;
            overviewData.mNeighboringTotalViewVisibility = View.VISIBLE;
        }
        mCellInfoAdapter.updateCellInfoOverview(overviewData);
    }

    void updateNeighbouringCells() {
        final List<String> list = rilExecutor.getNeighbours();
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (list != null) {
                    CellInfoOverviewData overviewData = new CellInfoOverviewData();
                    overviewData.mNeighboringCells = TextUtils.join("\n", list);
                    overviewData.mNeighboringCellsVisibility = View.VISIBLE;
                    overviewData.mNeighboringTotalViewVisibility = View.GONE;
                    if (mCellInfoAdapter != null) {
                        mCellInfoAdapter.updateCellInfoOverview(overviewData);
                    }
                }
            }
        });
    }

    void getSamSungMultiRil() {
        if (mBound && rilExecutor.mMultiRilCompatible) {
            new CellAsyncTask().execute(SAMSUNG_MULTIRIL_REQUEST);
        }
    }

    private class CellAsyncTask extends AsyncTask<Integer, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Integer... type) {
            switch (type[0]) {
                case STOCK_REQUEST:
                    return getStockNeighbouringCells();
                case SAMSUNG_MULTIRIL_REQUEST:
                    if (mBound) {
                        updateNeighbouringCells();
                        updateCipheringIndicator();
                    }
                    break;
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            if (result) {
                updateStockNeighbouringCells();
            } else {
                getSamSungMultiRil();
            }
        }
    }
    class CellInfoAdapter extends BaseAdapter {
        private final LayoutInflater mInflater;
        private final BaseInflaterAdapter<CardItemData> mCardItemDataAdapter;
        private CellInfoOverviewData mOverview;

        CellInfoAdapter(BaseInflaterAdapter<CardItemData> cardItemDataAdapter,
                        CellInfoOverviewData overview) {
            mCardItemDataAdapter = cardItemDataAdapter;
            mOverview = overview;
            mInflater = (LayoutInflater) getActivity().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }

        void updateCellInfoOverview(CellInfoOverviewData overview) {
            mOverview = overview;
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mCardItemDataAdapter.getCount() + 1;
        }

        @Override
        public Object getItem(int pos) {
            if (pos == 0) {
                return null;
            } else {
                return mCardItemDataAdapter.getItem(pos + 1);
            }
        }

        @Override
        public long getItemId(int pos) {
            return pos - 1;
        }

        @Override
        public int getItemViewType(int pos) {
            return pos == 0 ? 0 : 1;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @SuppressWarnings("WrongConstant")
        @Override
        public View getView(int pos, View convertView, ViewGroup parent) {
            if (pos == 0) {
                CellInfoOverviewHolder holder;
                if (convertView == null) {
                    convertView = mInflater.inflate(R.layout.item_cell_info_overview, parent, false);
                    holder = new CellInfoOverviewHolder(convertView);
                    convertView.setTag(holder);
                } else {
                    holder = (CellInfoOverviewHolder) convertView.getTag();
                }
                if (mOverview.mNeighboringCellsVisibility != -1) {
                    holder.mNeighboringCells.setVisibility(mOverview.mNeighboringCellsVisibility);
                    holder.mNeighboringCells.setText(mOverview.mNeighboringCells);
                }
                if (mOverview.mNeighboringTotalViewVisibility != -1) {
                    holder.mNeighboringTotalView.setVisibility(mOverview.mNeighboringTotalViewVisibility);
                    holder.mNeighboringTotal.setText(String.valueOf(mOverview.mNeighboringTotal));
                }
                if (mOverview.mCipheringIndicatorLabelVisibility != -1) {
                    holder.mCipheringIndicatorLabel.setVisibility(mOverview.mCipheringIndicatorLabelVisibility);
                }
                if (mOverview.mCipheringIndicatorVisibility != -1) {
                    holder.mCipheringIndicator.setVisibility(mOverview.mCipheringIndicatorVisibility);
                    holder.mCipheringIndicator.setText(mOverview.mCipheringIndicator);
                }
                return convertView;
            } else {
                return mCardItemDataAdapter.getView(pos - 1, convertView, parent);
            }
        }
    }

    class CellInfoOverviewData {
        String mNeighboringCells;
        int mNeighboringCellsVisibility = -1;

        int mNeighboringTotal;
        int mNeighboringTotalViewVisibility = -1;

        int mCipheringIndicatorLabelVisibility = -1;

        String mCipheringIndicator;
        int mCipheringIndicatorVisibility = -1;
    }

    class CellInfoOverviewHolder {
        TextView mNeighboringCells;
        TextView mNeighboringTotal;
        TableRow mNeighboringTotalView;
        TextView mCipheringIndicatorLabel;
        TextView mCipheringIndicator;

        CellInfoOverviewHolder(View view) {
            mNeighboringCells =         (TextView) view.findViewById(R.id.neighboring_cells);
            mNeighboringTotal =         (TextView) view.findViewById(R.id.neighboring_number);
            mNeighboringTotalView =     (TableRow) view.findViewById(R.id.neighboring_total);
            mCipheringIndicatorLabel =  (TextView) view.findViewById(R.id.ciphering_indicator_title);
            mCipheringIndicator =       (TextView) view.findViewById(R.id.ciphering_indicator);
        }
    }
} @XmlLayout(zz.aimsicd.lite.R.layout.fragment_db_viewer)
public final class DbViewerFragment extends InjectionFragment {

    private AIMSICDDbAdapter mDb;
    private StatesDbViewer mTableSelected;

    @InjectView(zz.aimsicd.lite.R.id.table_spinner)
    private Spinner tblSpinner;

    @InjectView(R.id.list_view)
    private ListView lv;

    @InjectView(zz.aimsicd.lite.R.id.db_list_empty)
    private View emptyView;

    public DbViewerFragment() {
    }

    // Todo: Let me try to fix this deprecation...
    @Override
    public void onAttach(Context activity) {
        super.onAttach(activity);
        mDb = new AIMSICDDbAdapter(activity);

    //public void onAttach(Activity activity) {
    //    super.onAttach(activity);
    //    mDb = new AIMSICDDbAdapter(activity.getBaseContext());
    }


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        DbViewerSpinnerAdapter mSpinnerAdapter = new DbViewerSpinnerAdapter(getActivity(), zz.aimsicd.lite.R.layout.item_spinner_db_viewer);
        tblSpinner.setAdapter(mSpinnerAdapter);
        tblSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parentView, View selectedItemView, final int position, long id) {
                Object selectedItem = tblSpinner.getSelectedItem();
                if (!(selectedItem instanceof StatesDbViewer)) {
                    return;
                }
                mTableSelected = (StatesDbViewer) selectedItem;
                new AsyncTask<Void, Void, BaseInflaterAdapter>() {

                    @Override
                    protected BaseInflaterAdapter doInBackground(Void... params) {
                        //# mDb.open();
                        Cursor result;

                        switch (position) {
                            case 0: // UNIQUE_BTS_DATA          ("DBi_bts")
                                // The unique BTSs we have been connected to in the past
                                result = mDb.returnDBiBts();
                                break;

                            case 1: // BTS_MEASUREMENTS:        ("DBi_measure")
                                // All BTS measurements we have done since start
                                result = mDb.returnDBiMeasure();
                                break;

                            case 2: // IMPORTED_OCID_DATA:      ("DBe_import")
                                // All Externally imported BTS data (from OCID, MLS, etc)
                                result = mDb.returnDBeImport();
                                break;

                            case 3: // DEFAULT_MCC_LOCATIONS:   ("defaultlocation")
                                // Default MCC location codes and approx corresponding GPS locations
                                result = mDb.returnDefaultLocation();
                                break;

                            case 4: // SILENT_SMS:              ("SmsData")
                                // SMS log data, such as SMSC, type, etc
                                result = mDb.returnSmsData();
                                break;

                            case 5: // MEASURED_SIGNAL_STRENGTHS: ("DBi_measure")
                                // TODO:     ToBe merged into "DBi_measure:rx_signal"
                                result = mDb.returnDBiMeasure();
                                break;

                            case 6: // EVENT_LOG:               ("EventLog")
                                // Table: "EventLog"
                                result = mDb.returnEventLogData();
                                break;
                            case 7:// SMS DETECTION STRINGS     ("DetectionStrings")
                                result = mDb.returnDetectionStrings();
                                break;

                            // TODO: Not yet implemented...leave as for time being
                            //case 8: // DETECTION_FLAGS:       ("DetectionFlags")
                            //    result = mDb.getDetectionFlagsData();
                            //    break;

                            default:
                                throw new IllegalArgumentException("Unknown type of table");
                        }

                        BaseInflaterAdapter adapter = null;
                        if (result != null) {
                            adapter = BuildTable(result);
                            result.close();
                        }

                        return adapter;
                    }

                    @Override
                    protected void onPostExecute(BaseInflaterAdapter adapter) {
                        if (getActivity() == null) {
                            return; // fragment detached
                        }

                        lv.setEmptyView(emptyView);
                        if (adapter != null) {
                            lv.setAdapter(adapter);
                            lv.setVisibility(View.VISIBLE);
                        } else {
                            lv.setVisibility(View.GONE);
                            emptyView.setVisibility(View.VISIBLE);
                        }

                        // ToDo: 2017-05-04
                        getActivity().setProgressBarIndeterminateVisibility(false);
                        /*getActivity().setSupportProgressBarIndeterminateVisibility(false);
                        getSupportActionBar().setDisplayShowCustomEnabled(true);
                        getSupportActionBar().setCustomView(R.layout.toolbar);*/
                    }
                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parentView) {
            }
        });
    }

    /**
     * Description:     Content layout and presentation of the Database Viewer
     *
     * This is where the text labels are created for each column in
     * the Database Viewer (DBV). For details of how this should be presented, see:
     * https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/234
     *
     * Lat/Lng:     Latitude / Longitude (We should use "Lon" instead of "Lng".)
     * AvgSignal:   Average Signal Strength
     * RSSI:        Received Signal Strength Indicator (previously "Signal Strength")
     * Can have different meanings on different RAN's, e.g. RSCP in UMTS.
     * RAN:         Radio Access Network (GSM, UMTS, LTE etc.)
     *
     * Notes:
     *
     * 1. Although "RAN" is more correct here, we'll use "RAT" (Radio Access Technology),
     * which is the more common terminology. Thus reverting.
     *
     * 2. Since Signal is not an "indicator" we should just call it "RSS" or "RXS"
     */
    private BaseInflaterAdapter BuildTable(Cursor tableData) {
        if (tableData != null && tableData.getCount() > 0) {
            switch (mTableSelected) {

                case UNIQUE_BTS_DATA: {     // DBi_bts

                    BaseInflaterAdapter<UniqueBtsItemData> adapter
                            = new BaseInflaterAdapter<>(new UniqueBtsCardInflater());
                    int count = tableData.getCount();
                    while (tableData.moveToNext()) {
                        UniqueBtsItemData data = new UniqueBtsItemData(
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_MCC))),   // MCC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_MNC))),   // MNC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_LAC))),   // LAC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_CID))),   // CID
                                Cell.validatePscValue(
                                        this.getContext(),
                                        tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_PSC))),          // PSC
                                //String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_T3212))), // T3212
                                //String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_A5x))),   // A5x
                                //String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_ST_id))), // ST_id
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_TIME_FIRST)),         // time_first
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_TIME_LAST)),          // time_last
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_LAT)),                // gps_lat
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_BTS_LON)),                // gps_lon
                                (tableData.getPosition() + 1) + " / " + count                                               // item:  "n/X"
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;

                }

                case BTS_MEASUREMENTS: {    // DBi_measure

                    BaseInflaterAdapter<BtsMeasureItemData> adapter
                            = new BaseInflaterAdapter<>(new BtsMeasureCardInflater());
                    int count = tableData.getCount();

                    while (tableData.moveToNext()) {
                        // WARNING! The ORDER and number of these are crucial, and need to correspond
                        // to what's found in:  BtsMeasureCardInflater.java and BtsMeasureItemData.java
                        BtsMeasureItemData data = new BtsMeasureItemData(
                                "bts_id: " + String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_BTS_ID))), // TODO: Wrong! Should be DBi_bts:CID
                                "n/a", // + tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_NC_LIST)),        // nc_list      TODO: fix
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_TIME)),                       // time
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_GPSD_LAT)),                   // gpsd_lat
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_GPSD_LON)),                   // gpsd_lon
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_GPSD_ACCURACY))), // gpsd_accu    GPS accuracy in [m]
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_GPSE_LAT)),                // gpse_lat     TODO: remove
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_GPSE_LON)),                // gpse_lon     TODO: remove
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_BB_POWER)),                // bb_power     BP power usage [mA]
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_BB_RF_TEMP)),              // bb_rf_temp   BP temperature [C]
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_TX_POWER)),                // tx_power     BP TX Power [dB?]
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_RX_SIGNAL)),                  // rx_signal    BP RX signal [dBm]
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_RX_STYPE)),                // rx_stype     BP RX signal type [dBm, ASU, RSSI etc]?
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_RAT)),                        // RAT          (Aka RAN = INTEGER)
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_BCCH)),                    // BCCH         Broadcast Channel
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_TMSI)),                    // TMSI         Temporary IMSI
                                // String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_TA))),         // TA           Timing Advance (GSM/LTE)
                                // String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_PD))),         // PD           Propagation Delay (LTE)
                                // String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_BER))),        // BER          Bit Error Rate
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_AVG_EC_NO)),               // AvgEcNo      Average Ec/No
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_IS_SUBMITTED))),  // isSubmitted
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_IS_NEIGHBOUR))),  // isNeighbour
                                // tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBI_MEASURE_CON_STATE)),               // con_state    AOS data/connection states
                                (tableData.getPosition() + 1) + " / " + count                                                           // item:  "n/X"
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;

                }

                case IMPORTED_OCID_DATA: {      // DBe_import
                    /*
                     * Table: DBe_import
                     *
                     *   CSV:  lat,lon,mcc,mnc,lac,cellid,averageSignalStrength,range,samples,changeable,radio,rnc,cid,psc,  tac,pci,sid,nid,bid
                     *
                     *   new:   DBe_import
                     *          _id,DBsource,RAT,MCC,MNC,LAC,CID,PSC,gps_lat,gps_lon,isGPSexact,avg_range,avg_signal,samples,time_first,time_last,rej_cause
                     *
                     *  Thus for OCID data we cannot use: time_first or time_last.
                     *
                     */
                    BaseInflaterAdapter<DbeImportItemData> adapter
                            = new BaseInflaterAdapter<>(new DbeImportCardInflater());
                    int count = tableData.getCount();

                    while (tableData.moveToNext()) {
                        // WARNING! The ORDER and number of these are crucial, and need to correspond
                        // to what's found in:  DbeImportCardInflater.java and DbeImportItemData.java
                        // MUST also correspond to the imported OCID CSV order... ???
                        DbeImportItemData data = new DbeImportItemData(
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_DBSOURCE)),                    // DBsource
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_RAT)),                         // RAT
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_MCC))),            // MCC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_MNC))),            // MNC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_LAC))),            // LAC
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_CID))),            // CID
                                Cell.validatePscValue(
                                        this.getContext(),
                                        tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_PSC))),               // PSC (UMTS only)
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_GPS_LAT)),                     // gps_lat
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_GPS_LON)),                     // gps_lon
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_IS_GPS_EXACT))),   // isGPSexact
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_AVG_RANGE))),      // avg_range
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_AVG_SIGNAL))),     // avg_signal
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_SAMPLES))),        // samples
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_TIME_FIRST)),                  // time_first
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_TIME_LAST)),                   // time_last
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DBE_IMPORT_REJ_CAUSE)),                   // rej_cause
                                (tableData.getPosition() + 1) + " / " + count                                                           // item:  "n/X"
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;
                }

                case DEFAULT_MCC_LOCATIONS: {       // defaultlocation

                    BaseInflaterAdapter<CardItemData> adapter
                            = new BaseInflaterAdapter<>(new DefaultLocationCardInflater());
                    int count = tableData.getCount();

                    while (tableData.moveToNext()) {
                        CardItemData data = new CardItemData(
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DEFAULT_LOCATION_COUNTRY)),           // country
                                String.valueOf(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.DEFAULT_LOCATION_MCC))),  // MCC
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DEFAULT_LOCATION_LAT)),               // lat
                                tableData.getString(tableData.getColumnIndex(DBTableColumnIds.DEFAULT_LOCATION_LON)),               // lon
                                (tableData.getPosition() + 1) + " / " + count                                                       // item:  "n/X"
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;
                }

                case SILENT_SMS: {                  // SmsData

                    BaseInflaterAdapter<CapturedSmsData> adapter
                            = new BaseInflaterAdapter<>(new CapturedSmsCardInflater());
                    if (tableData.getCount() > 0) {
                        while (tableData.moveToNext()) {
                            CapturedSmsData getdata = new CapturedSmsData();

                            // TODO: Add class and smsc
                            // TODO: Check order as in DB schema (ER diagram)
                            getdata.setSmsTimestamp(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_TIMESTAMP)));        // time
                            getdata.setSmsType(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_SMS_TYPE)));              // type
                            getdata.setSenderNumber(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_SENDER_NUMBER)));    // number
                            //getdata.setSenderNumber(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_SMSC)));           // smsc
                            getdata.setSenderMsg(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_SENDER_MSG)));          // message
                            //getdata.setSenderNumber(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_CLASS)));          // class
                            getdata.setCurrent_lac(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_LAC)));                  // lac
                            getdata.setCurrent_cid(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_CID)));                  // cid
                            getdata.setCurrent_nettype(tableData.getString(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_RAT)));           // rat
                            getdata.setCurrent_roam_status(tableData.getInt(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_ROAM_STATE)));   // isRoaming (BOOL)
                            getdata.setCurrent_gps_lat(tableData.getDouble(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_GPS_LAT)));       // gps_lat
                            getdata.setCurrent_gps_lon(tableData.getDouble(tableData.getColumnIndex(DBTableColumnIds.SMS_DATA_GPS_LON)));       // gps_lon

                            adapter.addItem(getdata, false);
                        }
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;
                }

                case MEASURED_SIGNAL_STRENGTHS: {       // DBi_measure:rx_signal

                    // TODO: merge into "DBi_measure:rx_signal"
                    BaseInflaterAdapter<MeasuredCellStrengthCardData> adapter
                            = new BaseInflaterAdapter<>(new MeasuredCellStrengthCardInflater());
                    //int count = tableData.getCount();
                    while (tableData.moveToNext()) {
                        MeasuredCellStrengthCardData data = new MeasuredCellStrengthCardData(
                                tableData.getInt(tableData.getColumnIndex("bts_id")),                           // TODO: CID
                                Integer.parseInt(tableData.getString(tableData.getColumnIndex("rx_signal"))),   // rx_signal
                                tableData.getString(tableData.getColumnIndex("time"))                           // time
                                //"" + (tableData.getPosition() + 1) + " / " + count                            // item:  "n/X"
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;

                }

                case EVENT_LOG: {           // EventLog

                    BaseInflaterAdapter<EventLogItemData> adapter
                            = new BaseInflaterAdapter<>(new EventLogCardInflater());
                    int count = tableData.getCount();

                    // WARNING: Must correspond with:  EventLogCardInflater  and  EventLogItemData
                    while (tableData.moveToNext()) {
                        EventLogItemData data = new EventLogItemData(
                                // Use the trick to automatically converting int/real to strings by adding empty ""s.
                                "" + tableData.getString(tableData.getColumnIndex("time")),             // time
                                "" + tableData.getInt(tableData.getColumnIndex("LAC")),                 // LAC
                                "" + tableData.getInt(tableData.getColumnIndex("CID")),                 // CID
                                "" + Cell.validatePscValue(
                                        this.getContext(),
                                        tableData.getInt(tableData.getColumnIndex("PSC"))),             // PSC
                                "" + tableData.getDouble(tableData.getColumnIndex("gpsd_lat")),         // gpsd_lat
                                "" + tableData.getDouble(tableData.getColumnIndex("gpsd_lon")),         // gpsd_lon
                                "" + tableData.getInt(tableData.getColumnIndex("gpsd_accu")),           // gpsd_accu (accuracy in [m])
                                "" + tableData.getInt(tableData.getColumnIndex("DF_id")),               // DF_id
                                "" + tableData.getString(tableData.getColumnIndex("DF_description")),   // TODO: DF_desc
                                "" + (tableData.getPosition() + 1) + " / " + count                      // item:  "n/X"
                        );
                        // TODO: Explain how to use this?
                        data.setIsFakeData(isExample(data));
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;
                }

                case DETECTION_STRINGS: {          // DetectionStrings
                    // Storage of Abnormal SMS detection strings
                    BaseInflaterAdapter<DetectionStringsData> adapter
                            = new BaseInflaterAdapter<>(new DetectionStringsCardInflater());
                    while (tableData.moveToNext()) {
                        DetectionStringsData data = new DetectionStringsData(
                                tableData.getString(tableData.getColumnIndex("det_str")),  // det_str
                                tableData.getString(tableData.getColumnIndex("sms_type"))  // sms_type
                        );
                        adapter.addItem(data, false);
                    }
                    if (!tableData.isClosed()) {
                        tableData.close();
                    }
                    return adapter;
                }
            }
        } else {
            return null;
        }
        return null;
    }

    /*=========================================================================
     *          Add Example entries into the Database Viewer tables
     *=========================================================================*/

    // Table:           EventLog
    // Dependencies:    Examples.java
    //                  EventLogItemData.java
    private boolean isExample(EventLogItemData pEventLogItemData) {
        return pEventLogItemData != null &&
                pEventLogItemData.getLac().contains(Examples.EVENT_LOG_DATA.LAC) &&
                pEventLogItemData.getCellID().contains(Examples.EVENT_LOG_DATA.CID) &&
                pEventLogItemData.getPsc().contains(Examples.EVENT_LOG_DATA.PSC) &&
                pEventLogItemData.getLat().contains(Examples.EVENT_LOG_DATA.GPSD_LAT) &&
                pEventLogItemData.getLng().contains(Examples.EVENT_LOG_DATA.GPSD_LON) &&
                pEventLogItemData.getgpsd_accu().contains(Examples.EVENT_LOG_DATA.GPSD_ACCU) &&
                pEventLogItemData.getDF_id().contains(Examples.EVENT_LOG_DATA.DF_ID);
    }

    // Table:           SmsData
    // Dependencies:    Examples.java
    //                  SilentSmsCardData.java
    private boolean isExample(SilentSmsCardData pSilentSmsCardData) {
        return pSilentSmsCardData != null &&
                pSilentSmsCardData.getAddress().contains(Examples.SILENT_SMS_CARD_DATA.ADDRESS) &&
                pSilentSmsCardData.getDisplayAddress().contains(Examples.SILENT_SMS_CARD_DATA.DISPLAY);
    }

} @XmlLayout(R.layout.fragment_device)
public class DeviceFragment extends InjectionFragment implements SwipeRefreshLayout.OnRefreshListener {

    public static final String TAG = "AICDL";
    public static final String mTAG = "DeviceFragment";



    @Inject
    OkHttpClient okHttpClient;

    @InjectView(R.id.swipeRefresLayout)
    private SwipeRefreshLayout swipeRefreshLayout;

    private AimsicdService mAimsicdService;
    private boolean mBound;
    private Context mContext;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mContext = getActivity().getBaseContext();
        // Bind to LocalService
        Intent intent = new Intent(mContext, AimsicdService.class);
        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        swipeRefreshLayout.setOnRefreshListener(this);
    }

    @Override
    public void onResume() {
        super.onResume();

        if (!mBound) {
            // Bind to LocalService
            Intent intent = new Intent(mContext, AimsicdService.class);
            mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
        updateUI();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Unbind from the service
        if (mBound) {
            mContext.unbindService(mConnection);
            mBound = false;
        }
    }

    /**
     * Service Connection to bind the activity to the service
     */
    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            mAimsicdService = ((AimsicdService.AimscidBinder) service).getService();
            mBound = true;
            updateUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
           Log.e(TAG, mTAG + "Service Disconnected");
            mBound = false;
        }
    };

    private void updateUI() {
        HighlightTextView content;
        TableRow tr;
        if (mBound) {
            final AnimationManager ani = new AnimationManager();

            mAimsicdService.getCellTracker().refreshDevice();
            Device mDevice = mAimsicdService.getCellTracker().getDevice();
            switch (mDevice.getPhoneID()) {

                case TelephonyManager.PHONE_TYPE_NONE:  // Maybe bad!
                case TelephonyManager.PHONE_TYPE_SIP:   // Maybe bad!
                case TelephonyManager.PHONE_TYPE_GSM: {
                    content = (HighlightTextView)  getView().findViewById(R.id.network_lac);
                    content.updateText(String.valueOf(mAimsicdService.getCell().getLAC()), ani);
                    tr = (TableRow) getView().findViewById(R.id.gsm_cellid);
                    tr.setVisibility(View.VISIBLE);
                    content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_cellid);
                    content.updateText(String.valueOf(mAimsicdService.getCell().getCID()), ani);
                    break;
                }

                case TelephonyManager.PHONE_TYPE_CDMA: {
                    tr = (TableRow) getView().findViewById(zz.aimsicd.lite.R.id.cdma_netid);
                    tr.setVisibility(View.VISIBLE);
                    content = (HighlightTextView)  getView().findViewById(R.id.network_netid);
                    content.updateText(String.valueOf(mAimsicdService.getCell().getLAC()), ani);
                    tr = (TableRow) getView().findViewById(R.id.cdma_sysid);
                    tr.setVisibility(View.VISIBLE);
                    content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_sysid);
                    content.updateText(String.valueOf(mAimsicdService.getCell().getSID()), ani);
                    tr = (TableRow) getView().findViewById(zz.aimsicd.lite.R.id.cdma_baseid);
                    tr.setVisibility(View.VISIBLE);
                    content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_baseid);
                    content.updateText(String.valueOf(mAimsicdService.getCell().getCID()), ani);
                    break;
                }
                default:
                    Log.e(TAG, mTAG + ": unknown phone type: " + mDevice.getPhoneID());
            }

            if (mAimsicdService.getCell().getTimingAdvance() != Integer.MAX_VALUE) {
                tr = (TableRow) getView().findViewById(R.id.lte_timing_advance);
                tr.setVisibility(View.VISIBLE);
                content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_lte_timing_advance);
                content.updateText(String.valueOf(mAimsicdService.getCell().getTimingAdvance()), ani);
            } else {
                tr = (TableRow) getView().findViewById(zz.aimsicd.lite.R.id.lte_timing_advance);
                tr.setVisibility(View.GONE);
            }

            if (mAimsicdService.getCell().getPSC() != Integer.MAX_VALUE) {
                content = (HighlightTextView)  getView().findViewById(R.id.network_psc);
                content.updateText(String.valueOf(mAimsicdService.getCell().getPSC()), ani);
                tr = (TableRow) getView().findViewById(zz.aimsicd.lite.R.id.primary_scrambling_code);
                tr.setVisibility(View.VISIBLE);
            }

            String notAvailable = getString(R.string.n_a);

            content = (HighlightTextView)  getView().findViewById(R.id.sim_country);
            content.updateText(mDevice.getSimCountry().orElse(notAvailable), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.sim_operator_id);
            content.updateText(mDevice.getSimOperator().orElse(notAvailable), ani);
            content = (HighlightTextView) getView().findViewById(zz.aimsicd.lite.R.id.sim_operator_name);
            content.updateText(mDevice.getSimOperatorName().orElse(notAvailable), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.sim_imsi);
            content.updateText(mDevice.getSimSubs().orElse(notAvailable), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.sim_serial);
            content.updateText(mDevice.getSimSerial().orElse(notAvailable), ani);

            content = (HighlightTextView)  getView().findViewById(R.id.device_type);
            content.updateText(mDevice.getPhoneType(), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.device_imei);
            content.updateText(mDevice.getIMEI(), ani);
            content = (HighlightTextView)  getView().findViewById(R.id.device_version);
            content.updateText(mDevice.getIMEIv(), ani);

            content = (HighlightTextView)  getView().findViewById(R.id.network_name);
            content.updateText(mDevice.getNetworkName(), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_code);
            content.updateText(mDevice.getMncMcc(), ani);
            content = (HighlightTextView)  getView().findViewById(R.id.network_type);
            content.updateText(mDevice.getNetworkTypeName(), ani);

            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.data_activity);
            content.updateText(mDevice.getDataActivity(), ani);
            content = (HighlightTextView)  getView().findViewById(R.id.data_status);
            content.updateText(mDevice.getDataState(), ani);
            content = (HighlightTextView)  getView().findViewById(zz.aimsicd.lite.R.id.network_roaming);
            content.updateText(mDevice.isRoaming(), ani);

            ani.startAnimation(5000);
        }
    }

    @Override
    public void onRefresh() {
        if (CellTracker.OCID_API_KEY != null && !CellTracker.OCID_API_KEY.equals("NA")) {
            Request request = createOpenCellIdApiCall();
            okHttpClient.newCall(request).enqueue(getOpenCellIdResponseCallback());
        } else {
            Handler refresh = new Handler(Looper.getMainLooper());
            refresh.post(new Runnable() {
                public void run() {
                    Helpers.sendMsg(getActivity(), getString(R.string.no_opencellid_key_detected));
                    swipeRefreshLayout.setRefreshing(false);
                }
            });
        }
    }

    @NonNull
    private Callback getOpenCellIdResponseCallback() {
        return new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Handler refresh = new Handler(Looper.getMainLooper());
                refresh.post(new Runnable() {
                    public void run() {
                        refreshFailed();
                    }
                });
            }

            @Override
            public void onResponse(final Response response) throws IOException {
                Handler refresh = new Handler(Looper.getMainLooper());
                refresh.post(new Runnable() {
                    public void run() {
                        Cell cell = responseToCell(response);
                        processFinish(cell);
                    }
                });
            }
        };
    }

    //TODO: Use Retrofit for this
    private Request createOpenCellIdApiCall() {
        StringBuilder sb = new StringBuilder();
        sb.append("http://www.opencellid.org/cell/get?key=").append(CellTracker.OCID_API_KEY);

        if (mAimsicdService.getCell().getMCC() != Integer.MAX_VALUE) {
            sb.append("&mcc=").append(mAimsicdService.getCell().getMCC());
        }
        if (mAimsicdService.getCell().getMNC() != Integer.MAX_VALUE) {
            sb.append("&mnc=").append(mAimsicdService.getCell().getMNC());
        }
        if (mAimsicdService.getCell().getLAC() != Integer.MAX_VALUE) {
            sb.append("&lac=").append(mAimsicdService.getCell().getLAC());
        }
        if (mAimsicdService.getCell().getCID() != Integer.MAX_VALUE) {
            sb.append("&cellid=").append(mAimsicdService.getCell().getCID());
        }
        sb.append("&format=json");
        return new Request.Builder()
                .url(sb.toString())
                .get()
                .build();
    }

    private Cell responseToCell(Response response) {
        try {
            JSONObject jsonCell = new JSONObject(response.body().string());
            Cell cell = new Cell();
            cell.setLat(jsonCell.getDouble("lat"));
            cell.setLon(jsonCell.getDouble("lon"));
            cell.setMCC(jsonCell.getInt("mcc"));
            cell.setMNC(jsonCell.getInt("mnc"));
            cell.setCID(jsonCell.getInt("cellid"));
            cell.setLAC(jsonCell.getInt("lac"));
            return cell;
        } catch (JSONException | IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void processFinish(Cell cell) {
        if (cell != null) {
            Log.i(TAG, mTAG + "processFinish - Cell =" + cell.toString());
            if (cell.isValid()) {
                mAimsicdService.setCell(cell);
                Helpers.msgShort(mContext, getActivity().getString(zz.aimsicd.lite.R.string.refreshed_cell_id_info));  // TODO re-translating other languages
                updateUI();
                swipeRefreshLayout.setRefreshing(false);
            }
        }
    }

    private void refreshFailed() {
        Helpers.msgShort(mContext, "Failed to refresh CellId. Check network connection.");
        swipeRefreshLayout.setRefreshing(false);
    }
} @XmlLayout(R.layout.activity_map_viewer)
public final class MapFragment extends InjectionFragment implements OnSharedPreferenceChangeListener {

    public static final String TAG = "AICDL";
    public static final String mTAG = "MapFragment";
    
    public static final String updateOpenCellIDMarkers = "update_open_cell_markers";

    @InjectView(R.id.mapview)
    private MapView mMap;
    private AIMSICDDbAdapter mDbHelper;
    private SharedPreferences prefs;
    private AimsicdService mAimsicdService;
    private boolean mBound;

    private GeoPoint loc = null;

    private MyLocationNewOverlay mMyLocationOverlay;
    private CompassOverlay mCompassOverlay;
    private CellTowerGridMarkerClusterer mCellTowerGridMarkerClusterer;
    private Menu mOptionsMenu;
    TelephonyManager tm;

    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
        @Override
        public void onServiceStateChanged(ServiceState serviceState) {
            loadEntries();
        }

        @Override
        public void onCellInfoChanged(List<CellInfo> cellInfo) {
            loadEntries();
        }
    };

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.i(TAG, mTAG + "Starting MapViewer");

        setUpMapIfNeeded();

        mDbHelper = new AIMSICDDbAdapter(getActivity());
        tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);

        // Bind to LocalService
        Intent intent = new Intent(getActivity(), AimsicdService.class);
        getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION |
                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public void onResume() {
        super.onResume();
        setUpMapIfNeeded();

        prefs = getActivity().getSharedPreferences(
                AimsicdService.SHARED_PREFERENCES_BASENAME, 0);
        prefs.registerOnSharedPreferenceChangeListener(this);

        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mMessageReceiver,
                new IntentFilter(updateOpenCellIDMarkers));

        if (!mBound) {
            // Bind to LocalService
            Intent intent = new Intent(getActivity(), AimsicdService.class);
            getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }

        loadPreferences();
        loadEntries();

        if (mCompassOverlay != null) {
            mCompassOverlay.enableCompass();
        }

        if (mMyLocationOverlay != null) {
            mMyLocationOverlay.enableMyLocation();
        }
    }


    @Override
    public void onDestroyView() {
        super.onDestroyView();

        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mMessageReceiver);
        if (mCompassOverlay != null) {
            mCompassOverlay.disableCompass();
        }

        if (mMyLocationOverlay != null) {
            mMyLocationOverlay.disableMyLocation();
        }

        if(prefs != null) {
            prefs.unregisterOnSharedPreferenceChangeListener(this);
        }

        // Unbind from the service
        if (mBound) {
            getActivity().unbindService(mConnection);
            mBound = false;
        }

        TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);

        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mMessageReceiver);
    }

    private final BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            loadEntries();
            if (BuildConfig.DEBUG && mCellTowerGridMarkerClusterer != null && mCellTowerGridMarkerClusterer.getItems() != null) {
                Log.i(TAG, mTAG + "mMessageReceiver CellTowerMarkers.invalidate() markers.size():" + mCellTowerGridMarkerClusterer.getItems().size());
            }

        }
    };

    /**
     * Service Connection to bind the activity to the service
     *
     * This seem to setup the connection and animates the map window movement to the
     * last known location.
     */
    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            mAimsicdService = ((AimsicdService.AimscidBinder) service).getService();
            mBound = true;

            // setup map
            GeoLocation lastKnown = mAimsicdService.lastKnownLocation();
            if (lastKnown != null) {
                mMap.getController().setZoom(16); // Initial Zoom level
                mMap.getController().animateTo(new GeoPoint(
                        lastKnown.getLatitudeInDegrees(),
                        lastKnown.getLongitudeInDegrees()));
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
           Log.e(TAG, mTAG + "Service Disconnected");
            mBound = false;
        }
    };

    // Load the default map type from preferences
    private void loadPreferences() {
        String mapTypePref = getResources().getString(R.string.pref_map_type_key);
        prefs = getActivity().getSharedPreferences(
                AimsicdService.SHARED_PREFERENCES_BASENAME, 0);
        if (prefs.contains(mapTypePref)) {
            int mapType = Integer.parseInt(prefs.getString(mapTypePref, "0"));
            setupMapType(mapType);
        }
    }

    private void setupMapType(int mapType) {
        mMap.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
        // There are two other map types (hybrid and satellite), but we don't use them
        // as they are redundant (hybrid) and broken (satellite).
        switch (mapType) {
            case 0:
                mMap.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
                break;
            case 1:
                mMap.setTileSource(TileSourceFactory.CYCLEMAP);
                break;
            default:
                mMap.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
                break;
        }
    }

    /**
     * Description:     Initialises the Map and sets initial options such as:
     *                      Zoom levels and controls
     *                      Compass
     *                      ScaleBar
     *                      Cluster Pin colors
     *                      Location update settings
     */
    private void setUpMapIfNeeded() {

        // Check if we were successful in obtaining the map.
        mMap.setBuiltInZoomControls(true);
        mMap.setMultiTouchControls(true);
        mMap.setMinZoomLevel(3);
        mMap.setMaxZoomLevel(19); // Latest OSM can go to 21!
        mMap.getTileProvider().createTileCache();
        mCompassOverlay = new CompassOverlay(getActivity(), new InternalCompassOrientationProvider(getActivity()), mMap);

        ScaleBarOverlay mScaleBarOverlay = new ScaleBarOverlay(getActivity());
        mScaleBarOverlay.setScaleBarOffset(getResources().getDisplayMetrics().widthPixels / 2, 10);
        mScaleBarOverlay.setCentred(true);

        // Sets cluster pin color
        mCellTowerGridMarkerClusterer = new CellTowerGridMarkerClusterer(getActivity());
        //BitmapDrawable mapPinDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.ic_map_pin_orange);
        BitmapDrawable mapPinDrawable = (BitmapDrawable) ResourcesCompat.getDrawable(getResources(), R.drawable.ic_map_pin_orange, null);
        mCellTowerGridMarkerClusterer.setIcon(mapPinDrawable == null ? null : mapPinDrawable.getBitmap());

        GpsMyLocationProvider gpsMyLocationProvider = new GpsMyLocationProvider(getActivity().getBaseContext());
        gpsMyLocationProvider.setLocationUpdateMinDistance(100); // [m]  // Set the minimum distance for location updates
        gpsMyLocationProvider.setLocationUpdateMinTime(10000);   // [ms] // Set the minimum time interval for location updates
        mMyLocationOverlay = new MyLocationNewOverlay(getActivity().getBaseContext(), gpsMyLocationProvider, mMap);
        mMyLocationOverlay.setDrawAccuracyEnabled(true);

        mMap.getOverlays().add(mCellTowerGridMarkerClusterer);
        mMap.getOverlays().add(mMyLocationOverlay);
        mMap.getOverlays().add(mCompassOverlay);
        mMap.getOverlays().add(mScaleBarOverlay);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        this.mOptionsMenu = menu;
        inflater.inflate(zz.aimsicd.lite.R.menu.fragment_map_menu, mOptionsMenu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
            case R.id.map_preferences:
                Intent intent = new Intent(getActivity(), MapPrefActivity.class);
                startActivity(intent);
                return true;
            case R.id.get_opencellid: {
                if (mBound) {
                    GeoLocation lastKnown = mAimsicdService.lastKnownLocation();
                    if (lastKnown != null) {
                        Helpers.msgLong(getActivity(),
                                getString(R.string.contacting_opencellid_for_data));
                        Cell cell;
                        cell = mAimsicdService.getCell();
                        cell.setLon(lastKnown.getLongitudeInDegrees());
                        cell.setLat(lastKnown.getLatitudeInDegrees());
                        setRefreshActionButtonState(true);
                        TinyDB.getInstance().putBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP, false);
                        Helpers.getOpenCellData((InjectionAppCompatActivity) getActivity(), cell, RequestTask.DBE_DOWNLOAD_REQUEST_FROM_MAP, mAimsicdService);
                        return true;
                    }
                }

                if (loc != null) {
                    Helpers.msgLong(getActivity(), getString(zz.aimsicd.lite.R.string.contacting_opencellid_for_data));
                    Cell cell = new Cell();
                    cell.setLat(loc.getLatitude());
                    cell.setLon(loc.getLongitude());
                    setRefreshActionButtonState(true);
                    TinyDB.getInstance().putBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP, false);
                    Helpers.getOpenCellData((InjectionAppCompatActivity) getActivity(), cell, RequestTask.DBE_DOWNLOAD_REQUEST_FROM_MAP, mAimsicdService);
                } else {
                    Helpers.msgLong(getActivity(),
                            getString(R.string.unable_to_determine_last_location));
                }
                return true;
            }
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Description:    Loads Signal Strength Database details to plot on the map,
     * only entries which have a location (lon, lat) are used.
     */
    private void loadEntries() {

        new AsyncTask<Void, Void, GeoPoint>() {
            @Override
            protected GeoPoint doInBackground(Void... voids) {
                //int signal;

                mCellTowerGridMarkerClusterer.getItems().clear();

                //New function only gets bts from DBe_import by sim network
                loadOcidMarkersByNetwork();

                List<CellTowerMarker> items = new LinkedList<>();

                Cursor c = null;
                try {
                    // Grab cell data from CELL_TABLE (cellinfo) --> DBi_bts
                    c = mDbHelper.getCellData();
                } catch (IllegalStateException ix) {
                   Log.e(TAG, mTAG + "Problem getting data from CELL_TABLE", ix);
                }

                /*
                    This function is getting cells we logged from DBi_bts
                 */
                if (c != null && c.moveToFirst()) {
                    do {
                        if (isCancelled() || !isAdded()) {
                            return null;
                        }
                        // The indexing here is that of DB table
                        final int cellID = c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_CID));     // CID
                        final int lac = c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_LAC));        // LAC
                        final int mcc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_MCC));        // MCC
                        final int mnc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_MNC));        // MNC
                        final int psc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_PSC));        // PSC
                        final String rat = Cell.getRatFromInt(
                                c.getInt(c.getColumnIndex(DBTableColumnIds.DBI_BTS_JOINED_RAT)));        // RAT
                        final double dLat = c.getDouble(c.getColumnIndex(DBTableColumnIds.DBI_BTS_LAT)); // Lat
                        final double dLng = c.getDouble(c.getColumnIndex(DBTableColumnIds.DBI_BTS_LON)); // Lon

                        if (Double.doubleToRawLongBits(dLat) == 0
                                && Double.doubleToRawLongBits(dLng) == 0) {
                            continue;
                        }
                        // TODO this (signal) is not in DBi_bts
                        // signal = 1;
                        //c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_AVG_SIGNAL));  // signal
                        // In case of missing or negative signal, set a default fake signal,
                        // so that we can still draw signal circles.  ?
                        //if (signal <= 0) {
                        //    signal = 20;
                        //}

                        if (Double.doubleToRawLongBits(dLat) != 0
                                || Double.doubleToRawLongBits(dLng) != 0) {
                            loc = new GeoPoint(dLat, dLng);

                            CellTowerMarker ovm = new CellTowerMarker(getActivity(), mMap,
                                    "Cell ID: " + cellID,
                                    "", loc,
                                    new MarkerData(
                                            getContext(),
                                            String.valueOf(cellID),
                                            String.valueOf(loc.getLatitude()),
                                            String.valueOf(loc.getLongitude()),
                                            String.valueOf(lac),
                                            String.valueOf(mcc),
                                            String.valueOf(mnc),
                                            String.valueOf(psc),
                                            rat,
                                            "", false)
                            );
                            // The pin of our current position
                            //ovm.setIcon(getResources().getDrawable(R.drawable.ic_map_pin_blue));
                            ovm.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_map_pin_blue, null));

                            items.add(ovm);
                        }

                    } while (c.moveToNext());
                } else {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Helpers.msgLong(getActivity(), getString(R.string.no_tracked_locations_found));
                        }
                    });
                }

                GeoPoint ret = new GeoPoint(0, 0);
                if (mBound) {
                    try {
                        int mcc = mAimsicdService.getCell().getMCC();
                        double[] d = mDbHelper.getDefaultLocation(mcc);
                        ret = new GeoPoint(d[0], d[1]);
                    } catch (Exception e) {
                       Log.e(TAG, mTAG + "Error getting default location!", e);
                    }
                }
                if (c != null) {
                    c.close();
                }
                // plot neighbouring cells
                while (mAimsicdService == null) {
                    try {
                        if (isCancelled() || !isAdded()) {
                            return null;
                        }
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                       Log.w(TAG, mTAG + "thread interrupted", e);
                    }
                }
                List<Cell> nc = mAimsicdService.getCellTracker().updateNeighbouringCells();
                for (Cell cell : nc) {
                    if (isCancelled() || !isAdded()) {
                        return null;
                    }
                    try {
                        loc = new GeoPoint(cell.getLat(), cell.getLon());
                        CellTowerMarker ovm = new CellTowerMarker(getActivity(), mMap,
                                getString(R.string.cell_id_label) + cell.getCID(),
                                "", loc,
                                new MarkerData(
                                        getContext(),
                                        String.valueOf(cell.getCID()),
                                        String.valueOf(loc.getLatitude()),
                                        String.valueOf(loc.getLongitude()),
                                        String.valueOf(cell.getLAC()),
                                        String.valueOf(cell.getMCC()),
                                        String.valueOf(cell.getMNC()),
                                        String.valueOf(cell.getPSC()),
                                        String.valueOf(cell.getRAT()),
                                        "", false));

                        // The pin of other BTS
                        //ovm.setIcon(getResources().getDrawable(R.drawable.ic_map_pin_orange));
                        ovm.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_map_pin_orange, null));
                        items.add(ovm);
                    } catch (Exception e) {
                       Log.e(TAG, mTAG + "Error plotting neighbouring cells", e);
                    }
                }

                mCellTowerGridMarkerClusterer.addAll(items);

                return ret;
            }

            /**
             *  TODO:  We need a manual way to add our own location in case:
             *          a) GPS is jammed or not working
             *          b) WiFi location is not used
             *          c) Default MCC is too far off
             *
             * @param defaultLoc Default location to open map on
             */
            @Override
            protected void onPostExecute(GeoPoint defaultLoc) {
                if (loc != null && (Double.doubleToRawLongBits(loc.getLatitude()) != 0
                        && Double.doubleToRawLongBits(loc.getLongitude()) != 0)) {
                    mMap.getController().setZoom(16);
                    mMap.getController().animateTo(new GeoPoint(loc.getLatitude(), loc.getLongitude()));
                } else {
                    if (mBound) {
                        // Try and find last known location and zoom there
                        GeoLocation lastLoc = mAimsicdService.lastKnownLocation();
                        if (lastLoc != null) {
                            loc = new GeoPoint(lastLoc.getLatitudeInDegrees(),
                                    lastLoc.getLongitudeInDegrees());

                            mMap.getController().setZoom(16);
                            mMap.getController().animateTo(new GeoPoint(loc.getLatitude(), loc.getLongitude()));
                        } else {
                            //Use MCC to move camera to an approximate location near Countries Capital
                            loc = defaultLoc;

                            mMap.getController().setZoom(12);
                            mMap.getController().animateTo(new GeoPoint(loc.getLatitude(), loc.getLongitude()));
                        }
                    }
                }
                if (mCellTowerGridMarkerClusterer != null) {
                    if (BuildConfig.DEBUG && mCellTowerGridMarkerClusterer.getItems() != null) {
                        Log.i(TAG, mTAG + "CellTowerMarkers.invalidate() markers.size():" + mCellTowerGridMarkerClusterer.getItems().size());
                    }
                    //Drawing markers of cell tower immediately as possible
                    mCellTowerGridMarkerClusterer.invalidate();
                }
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    private void loadOcidMarkersByNetwork() {
        // Check if OpenCellID data exists and if so load this now
        List<CellTowerMarker> items = new LinkedList<>();
        String networkOperator = tm.getNetworkOperator();
        int currentMmc = 0;
        int currentMnc = 0;
        if (networkOperator != null && networkOperator.length() > 3) {
            currentMmc = Integer.parseInt(networkOperator.substring(0, 3));
            currentMnc = Integer.parseInt(networkOperator.substring(3));
        }
        // DBe_import tower pins.
        //Drawable cellTowerMarkerIcon = getResources().getDrawable(R.drawable.ic_map_pin_green);
        Drawable cellTowerMarkerIcon = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_map_pin_green, null);

        Cursor c = mDbHelper.returnOcidBtsByNetwork(currentMmc, currentMnc);
        if (c.moveToFirst()) {
            do {
                // CellID,Lac,Mcc,Mnc,Lat,Lng,AvgSigStr,Samples
                final int cellID = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_CID));
                final int lac = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_LAC));
                final int mcc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_MCC));
                final int mnc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_MNC));
                final int psc = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_PSC));
                final String rat = c.getString(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_RAT));
                final double dLat = Double.parseDouble(c.getString(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_GPS_LAT)));
                final double dLng = Double.parseDouble(c.getString(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_GPS_LON)));
                final GeoPoint location = new GeoPoint(dLat, dLng);
                //where is c.getString(6)AvgSigStr
                final int samples = c.getInt(c.getColumnIndex(DBTableColumnIds.DBE_IMPORT_SAMPLES));
                // Add map marker for CellID
                CellTowerMarker ovm = new CellTowerMarker(getActivity(), mMap,
                        "Cell ID: " + cellID,
                        "", location,
                        new MarkerData(
                                getContext(),
                                String.valueOf(cellID),
                                String.valueOf(location.getLatitude()),
                                String.valueOf(location.getLongitude()),
                                String.valueOf(lac),
                                String.valueOf(mcc),
                                String.valueOf(mnc),
                                String.valueOf(psc),
                                rat,
                                String.valueOf(samples),
                                false));

                ovm.setIcon(cellTowerMarkerIcon);
                items.add(ovm);
            } while (c.moveToNext());
        }
        c.close();

        mCellTowerGridMarkerClusterer.addAll(items);
    }

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        final String KEY_MAP_TYPE = getActivity().getBaseContext().getString(R.string.pref_map_type_key);
        if (key.equals(KEY_MAP_TYPE)) {
            int item = Integer.parseInt(sharedPreferences.getString(key, "0"));
            setupMapType(item);
        }
    }

    public void setRefreshActionButtonState(final boolean refreshing) {
        if (mOptionsMenu != null) {
            final MenuItem refreshItem = mOptionsMenu.findItem(R.id.get_opencellid);
            if (refreshItem != null) {
                if (refreshing) {
                    refreshItem.setActionView(R.layout.actionbar_indeterminate_progress);
                } else {
                    refreshItem.setActionView(null);
                }
            }
        }
    }


    @Override
    public void onStart() {
        super.onStart();
        ((AppAIMSICD) getActivity().getApplication()).attach((InjectionAppCompatActivity) getActivity());
        if (TinyDB.getInstance().getBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP)) {
            setRefreshActionButtonState(false);
        }
    }
} public interface AsyncResponse {
    void processFinish(float[] output);
    void processFinish(List<Cell> cells);
} public class Cell implements Parcelable {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";

    public static final String INVALID_PSC = "invalid";

    // Cell Specific Variables
    private int cid;                // Cell Identification code
    private int lac;                // Location Area Code
    private int mcc;                // Mobile Country Code
    private int mnc;                // Mobile Network Code
    private int dbm;                // [dBm] RX signal "power"
    private int psc;                // Primary Scrambling Code
    private int rssi;               // Relative Signal Strength Indicator [dBm, asu etc.]
    private int timingAdvance;      // Timing Advance [LTE,GSM]
    private int sid;                // Cell-ID for [CDMA]
    private long timestamp;         // time

    // Tracked Cell Specific Variables
    private int netType;
    private double speed;       //
    private double accuracy;    //
    private double bearing;     //
    private double lon;
    private double lat;

    {
        cid = Integer.MAX_VALUE;
        lac = Integer.MAX_VALUE;
        mcc = Integer.MAX_VALUE;
        mnc = Integer.MAX_VALUE;
        dbm = Integer.MAX_VALUE;
        psc = Integer.MAX_VALUE;
        rssi = Integer.MAX_VALUE;
        timingAdvance = Integer.MAX_VALUE;
        sid = Integer.MAX_VALUE;
        netType = Integer.MAX_VALUE;
        lon = 0.0;
        lat = 0.0;
        speed = 0.0;
        accuracy = 0.0;
        bearing = 0.0;
    }

    public Cell() {
    }

    public Cell(int cid, int lac, int mcc, int mnc, int dbm, long timestamp) {
        super();
        this.cid = cid;
        this.lac = lac;
        this.mcc = mcc;
        this.mnc = mnc;
        this.dbm = dbm;
        this.rssi = Integer.MAX_VALUE;
        this.psc = Integer.MAX_VALUE;
        this.timestamp = timestamp;
        this.timingAdvance = Integer.MAX_VALUE;
        this.sid = Integer.MAX_VALUE;
        this.netType = Integer.MAX_VALUE;
        this.lon = 0.0;
        this.lat = 0.0;
        this.speed = 0.0;
        this.accuracy = 0.0;
        this.bearing = 0.0;
    }

    public Cell(int cid, int lac, int signal, int psc, int netType, boolean dbm) {
        this.cid = cid;
        this.lac = lac;
        this.mcc = Integer.MAX_VALUE;
        this.mnc = Integer.MAX_VALUE;

        if (dbm) {
            this.dbm = signal;
        } else {
            this.rssi = signal;
        }
        this.psc = psc;

        this.netType = netType;
        this.timingAdvance = Integer.MAX_VALUE;
        this.sid = Integer.MAX_VALUE;
        this.lon = 0.0;
        this.lat = 0.0;
        this.speed = 0.0;
        this.accuracy = 0.0;
        this.bearing = 0.0;
        this.timestamp = SystemClock.currentThreadTimeMillis();
    }

    public Cell(int cid, int lac, int mcc, int mnc, int dbm, double accuracy, double speed,
                double bearing, int netType, long timestamp) {
        this.cid = cid;
        this.lac = lac;
        this.mcc = mcc;
        this.mnc = mnc;
        this.dbm = dbm;
        this.rssi = Integer.MAX_VALUE;
        this.timingAdvance = Integer.MAX_VALUE;
        this.sid = Integer.MAX_VALUE;
        this.accuracy = accuracy;
        this.speed = speed;
        this.bearing = bearing;
        this.netType = netType;
        this.timestamp = timestamp;
    }

    /**
     * Current Cell ID
     *
     * @return int representing the Cell ID from GSM or CDMA devices
     */
    public int getCID() {
        return this.cid;
    }

    /**
     * Sets current Cell ID
     *
     * @param cid Cell ID
     */
    public void setCID(int cid) {
        this.cid = cid;
    }

    /**
     * Location Area Code (LAC) of current Cell
     *
     * @return int Current cells Location Area Code
     */
    public int getLAC() {
        return this.lac;
    }

    /**
     * Set Location Area Code (LAC) of current Cell
     *
     * @param lac Location Area Code
     */
    public void setLAC(int lac) {
        this.lac = lac;
    }

    /**
     * Mobile Country Code (Mcc) of current Cell
     *
     * @return int Current cells Mobile Country Code
     */
    public int getMCC() {
        return this.mcc;
    }

    /**
     * Set Mobile Country Code (Mcc) of current Cell
     *
     * @param mcc Mobile Country Code
     */
    public void setMCC(int mcc) {
        this.mcc = mcc;
    }

    /**
     * Mobile Network Code (Mnc) of current Cell
     *
     * @return int Current cells Mobile Network Code
     */
    public int getMNC() {
        return this.mnc;
    }

    /**
     * Set Mobile Network Code (Mnc) of current Cell
     *
     * @param mnc Mobile Network Code
     */
    public void setMNC(int mnc) {
        this.mnc = mnc;
    }

    /**
     * Primary Scrambling Code (PSC) of current Cell
     *
     * @return int Current cells Primary Scrambling Code
     */
    public int getPSC() {
        return this.psc;
    }

    /**
     * Set Primary Scrambling Code (PSC) of current Cell
     *
     * @param psc Primary Scrambling Code
     */
    public void setPSC(int psc) {
        if (psc == -1) {
            this.psc = Integer.MAX_VALUE;
        } else {
            this.psc = psc;
        }
    }

    /**
     * Radio Access Technology (RAT)
     *
     * Some places in the app refers to this as the Network Type.
     *
     * For our purposes, network types displayed to the user is referred to as RAT.
     *
     * @return Current cell's Radio Access Technology (e.g. UMTS, GSM) or null if not known
     */
    public String getRAT() {
        return getRatFromInt(this.netType);
    }

    /**
     * CDMA System ID
     *
     * @return System ID or Integer.MAX_VALUE if not supported
     */
    public int getSID() {
        return this.sid;
    }

    /**
     * Set CDMA System ID (SID) of current Cell
     *
     * @param sid CDMA System ID
     */
    public void setSID(int sid) {
        this.sid = sid;
    }

    /**
     * Signal Strength Measurement (dBm)
     *
     * @return Signal Strength Measurement (dBm)
     */
    public int getDBM() {
        return dbm;
    }

    /**
     * Set Signal Strength (dBm) of current Cell
     *
     * @param dbm Signal Strength (dBm)
     */
    public void setDBM(int dbm) {
        this.dbm = dbm;
    }

    /**
     * Longitude Geolocation of current Cell
     *
     * @return Longitude
     */
    public double getLon() {
        return this.lon;
    }

    /**
     * Set Longitude Geolocation of current Cell
     *
     * @param lon Longitude
     */
    public void setLon(double lon) {
        this.lon = lon;
    }

    /**
     * Latitude Geolocation of current Cell
     *
     * @return Latitude
     */
    public double getLat() {
        return this.lat;
    }

    /**
     * Set Latitude Geolocation of current Cell
     *
     * @param lat Latitude
     */
    public void setLat(double lat) {
        this.lat = lat;
    }

    /**
     * Ground speed in metres/second
     *
     * @return Ground speed or 0.0 if unavailable
     */
    public double getSpeed() {
        return this.speed;
    }

    /**
     * Set current ground speed in metres/second
     *
     * @param speed Ground Speed
     */
    public void setSpeed(double speed) {
        this.speed = speed;
    }

    /**
     * Accuracy of location in metres
     *
     * @return Location accuracy in metres or 0.0 if unavailable
     */
    public double getAccuracy() {
        return this.accuracy;
    }

    /**
     * Set current location accuracy in metres
     *
     * @param accuracy Location accuracy
     */
    public void setAccuracy(double accuracy) {
        this.accuracy = accuracy;
    }

    /**
     * Set current Bearing
     *
     * @param bearing Current bearing
     */
    public void setBearing(double bearing) {
        this.bearing = bearing;
    }

    /**
     * LTE Timing Advance
     *
     * @return LTE Timing Advance or Integer.MAX_VALUE if unavailable
     */
    public int getTimingAdvance() {
        return this.timingAdvance;
    }

    /**
     * Set current LTE Timing Advance
     *
     * @param ta Current LTE Timing Advance
     */
    public void setTimingAdvance(int ta) {
        this.timingAdvance = ta;
    }

    /**
     * Network Type
     *
     * @return string representing device Network Type
     */
    public int getNetType() {
        return this.netType;
    }

    /**
     * Set current Network Type
     *
     * @param netType Current Network Type
     */
    public void setNetType(int netType) {
        this.netType = netType;
    }

    /**
     * Timestamp of current cell information
     *
     * @return Timestamp
     */
    public long getTimestamp() {
        return this.timestamp;
    }

    /**
     * Set current cell information timestamp
     *
     * @param timestamp Current cell information timestamp
     */
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * Received Signal Strength
     *
     * @return Received Signal Strength Indicator (RSSI)
     */
    public int getRssi() {
        return this.rssi;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + cid;
        result = prime * result + lac;
        result = prime * result + mcc;
        result = prime * result + mnc;
        if (psc != -1) {
            result = prime * result + psc;
        }
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (((Object) this).getClass() != obj.getClass()) {
            return false;
        }
        Cell other = (Cell) obj;
        if (this.psc != Integer.MAX_VALUE) {
            return this.cid == other.getCID() && this.lac == other.getLAC() && this.mcc == other
                    .getMCC() && this.mnc == other.getMNC() && this.psc == other.getPSC();
        } else {
            return this.cid == other.getCID() && this.lac == other.getLAC() && this.mcc == other
                    .getMCC() && this.mnc == other.getMNC();
        }
    }

    public String toString() {
        StringBuilder result = new StringBuilder();

        result.append("CID - ").append(cid).append("\n");
        result.append("LAC - ").append(lac).append("\n");
        result.append("MCC - ").append(mcc).append("\n");
        result.append("MNC - ").append(mnc).append("\n");
        result.append("DBm - ").append(dbm).append("\n");
        result.append("PSC - ").append(validatePscValue(psc)).append("\n");
        result.append("Type - ").append(netType).append("\n");
        result.append("Lon - ").append(lon).append("\n");
        result.append("Lat - ").append(lat).append("\n");

        return result.toString();
    }

    public boolean isValid() {
        return this.getCID() != Integer.MAX_VALUE && this.getLAC() != Integer.MAX_VALUE;
    }

    /**
     * Get a human-readable string of RAT/Network Type
     *
     * Frustratingly it looks like the app uses RAT & Network Type interchangably with both either
     * being an integer representation (TelephonyManager's constants) or a human-readable string.
     *
     * @param netType The integer representation of the network type, via TelephonyManager
     * @return Human-readable representation of network type (e.g. "EDGE", "LTE")
     */
    public static String getRatFromInt(int netType) {
        switch (netType) {
            case TelephonyManager.NETWORK_TYPE_1xRTT:
                return "1xRTT";
            case TelephonyManager.NETWORK_TYPE_CDMA:
                return "CDMA";
            case TelephonyManager.NETWORK_TYPE_EDGE:
                return "EDGE";
            case TelephonyManager.NETWORK_TYPE_EHRPD:
                return "eHRPD";
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
                return "EVDO rev. 0";
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
                return "EVDO rev. A";
            case TelephonyManager.NETWORK_TYPE_EVDO_B:
                return "EVDO rev. B";
            case TelephonyManager.NETWORK_TYPE_GPRS:
                return "GPRS";
            case TelephonyManager.NETWORK_TYPE_HSDPA:
                return "HSDPA";
            case TelephonyManager.NETWORK_TYPE_HSPA:
                return "HSPA";
            case TelephonyManager.NETWORK_TYPE_HSPAP:
                return "HSPA+";
            case TelephonyManager.NETWORK_TYPE_HSUPA:
                return "HSUPA";
            case TelephonyManager.NETWORK_TYPE_IDEN:
                return "iDen";
            case TelephonyManager.NETWORK_TYPE_LTE:
                return "LTE";
            case TelephonyManager.NETWORK_TYPE_UMTS:
                return "UMTS";
            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                return "Unknown";
            default:
                return String.valueOf(netType);
        }
    }

    public static String validatePscValue(Context c, String psc) {
        return validatePscValue(c, Integer.parseInt(psc));
    }

    /**
     * Validate PSC is in bounds, return i18n'd "Unknown" if invalid
     *
     * @see #validatePscValue(int)
     *
     * @param c Used for getString translations
     * @param psc
     * @return PSC or "Unknown "if invalid
     */
    public static String validatePscValue(Context c, int psc) {
        String s = validatePscValue(psc);
        if (s.equals(INVALID_PSC)) {
            return c.getString(R.string.unknown);
        }
        return s;
    }

    public static String validatePscValue(String psc) {
        return validatePscValue(Integer.parseInt(psc));
    }

    /**
     * Validate PSC is in bounds
     *
     * Database import stores cell's PSC as "666" if its absent in OCID. This method will return
     * "invalid" instead.
     *
     * Use this method to translate/i18n a cell's missing PSC value.
     *
     * @param psc
     * @return PSC or "invalid" untranslated string if invalid
     */
    public static String validatePscValue(int psc) {
        if (psc < 0 || psc > 511) {
            return INVALID_PSC;
        }
        return String.valueOf(psc);
    }

    // Parcelling
    public Cell(Parcel in) {
        String[] data = new String[15];

        in.readStringArray(data);
        cid = Integer.valueOf(data[0]);
        lac = Integer.valueOf(data[1]);
        mcc = Integer.valueOf(data[2]);
        mnc = Integer.valueOf(data[3]);
        dbm = Integer.valueOf(data[4]);
        psc = Integer.valueOf(data[5]);
        rssi = Integer.valueOf(data[6]);
        timingAdvance = Integer.valueOf(data[7]);
        sid = Integer.valueOf(data[8]);
        netType = Integer.valueOf(data[9]);
        lon = Double.valueOf(data[10]);
        lat = Double.valueOf(data[11]);
        speed = Double.valueOf(data[12]);
        accuracy = Double.valueOf(data[13]);
        bearing = Double.valueOf(data[14]);
    }

    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStringArray(new String[]{
                String.valueOf(this.cid),
                String.valueOf(this.lac),
                String.valueOf(this.mcc),
                String.valueOf(this.mnc),
                String.valueOf(this.dbm),
                String.valueOf(this.psc),
                String.valueOf(this.rssi),
                String.valueOf(this.timingAdvance),
                String.valueOf(this.sid),
                String.valueOf(this.netType),
                String.valueOf(this.lon),
                String.valueOf(this.lat),
                String.valueOf(this.speed),
                String.valueOf(this.accuracy),
                String.valueOf(this.bearing)});
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public Cell createFromParcel(Parcel in) {
            return new Cell(in);
        }

        public Cell[] newArray(int size) {
            return new Cell[size];
        }
    };
} public class GeoLocation {

    private double radLat; // latitude in radians
    private double radLon; // longitude in radians
    private double degLat; // latitude in degrees
    private double degLon; // longitude in degrees

    private static final double MIN_LAT = Math.toRadians(-90d);  // -PI/2
    private static final double MAX_LAT = Math.toRadians(90d);   // PI/2
    private static final double MIN_LON = Math.toRadians(-180d); // -PI
    private static final double MAX_LON = Math.toRadians(180d);  // PI

    private GeoLocation() {
    }

    /**
     * @param latitude  the latitude, in degrees.
     * @param longitude the longitude, in degrees.
     */
    public static GeoLocation fromDegrees(double latitude, double longitude) {
        GeoLocation result = new GeoLocation();
        result.radLat = Math.toRadians(latitude);
        result.radLon = Math.toRadians(longitude);
        result.degLat = latitude;
        result.degLon = longitude;
        result.checkBounds();
        return result;
    }

    /**
     * @param latitude  the latitude, in radians.
     * @param longitude the longitude, in radians.
     */
    public static GeoLocation fromRadians(double latitude, double longitude) {
        GeoLocation result = new GeoLocation();
        result.radLat = latitude;
        result.radLon = longitude;
        result.degLat = Math.toDegrees(latitude);
        result.degLon = Math.toDegrees(longitude);
        result.checkBounds();
        return result;
    }

    private void checkBounds() {
        if (radLat < MIN_LAT || radLat > MAX_LAT ||
                radLon < MIN_LON || radLon > MAX_LON) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * @return the latitude, in degrees.
     */
    public double getLatitudeInDegrees() {
        return degLat;
    }

    /**
     * @return the longitude, in degrees.
     */
    public double getLongitudeInDegrees() {
        return degLon;
    }

    /**
     * @return the latitude, in radians.
     */
    public double getLatitudeInRadians() {
        return radLat;
    }

    /**
     * @return the longitude, in radians.
     */
    public double getLongitudeInRadians() {
        return radLon;
    }

    @Override
    public String toString() {
        return "(" + degLat + "\u00B0, " + degLon + "\u00B0) = (" +
                radLat + " rad, " + radLon + " rad)";
    }

    /**
     * Computes the great circle distance between this GeoLocation instance and
     * the location argument.
     *
     * @param radius the radius of the sphere, e.g. the average radius for a
     *               spherical approximation of the figure of the Earth is
     *               approximately 6371.01 kilometers.
     *
     * @return the distance, measured in the same unit as the radius argument.
     */
    public double distanceTo(GeoLocation location, double radius) {
        return Math.acos(Math.sin(radLat) * Math.sin(location.radLat) +
                Math.cos(radLat) * Math.cos(location.radLat) *
                        Math.cos(radLon - location.radLon)) * radius;
    }

    /**
     * Computes the bounding coordinates of all points on the surface of a
     * sphere that have a great circle distance to the point represented by this
     * GeoLocation instance that is less or equal to the distance argument.
     *
     *
     * @param distance the distance from the point represented by this
     *                 GeoLocation instance. Must me measured in the same
     *                 unit as the radius argument.
     *
     * @param radius   the radius of the sphere, e.g. the average radius for a
     *                 spherical approximation of the figure of the Earth is
     *                 approximately 6371.01 kilometers.
     *
     * @return an array of two GeoLocation objects such that:
     *
     * The latitude of any point within the specified distance is
     * greater or equal to the latitude of the first array element and
     * smaller or equal to the latitude of the second array element.
     *
     * If the longitude of the first array element is smaller or
     * equal to the longitude of the second element, then the longitude
     * of any point within the specified distance is greater or equal to
     * the longitude of the first array element and smaller or equal to
     * the longitude of the second array element.
     *
     * If the longitude of the first array element is greater than
     * the longitude of the second element (this is the case if the
     * 180th meridian is within the distance), then the longitude of any
     * point within the specified distance is greater or equal to the
     * longitude of the first array element OR smaller
     * or equal to the longitude of the second array element.
     *
     */
    public GeoLocation[] boundingCoordinates(double distance, double radius) {

        if (radius < 0d || distance < 0d) {
            throw new IllegalArgumentException();
        }

        // angular distance in radians on a great circle
        double radDist = distance / radius;

        double minLat = radLat - radDist;
        double maxLat = radLat + radDist;

        double minLon, maxLon;
        if (minLat > MIN_LAT && maxLat < MAX_LAT) {
            double deltaLon = Math.asin(Math.sin(radDist) /
                    Math.cos(radLat));
            minLon = radLon - deltaLon;
            if (minLon < MIN_LON) {
                minLon += 2d * Math.PI;
            }
            maxLon = radLon + deltaLon;
            if (maxLon > MAX_LON) {
                maxLon -= 2d * Math.PI;
            }
        } else {
            // a pole is within the distance
            minLat = Math.max(minLat, MIN_LAT);
            maxLat = Math.min(maxLat, MAX_LAT);
            minLon = MIN_LON;
            maxLon = MAX_LON;
        }

        return new GeoLocation[]{
                fromRadians(minLat, minLon),
                fromRadians(maxLat, maxLon)
        };
    }

}  public class Helpers {

    public static final String TAG = "AICDL";
    public static final String mTAG = "Helpers: ";

    private static final int CHARS_PER_LINE = 34;

    /**
     * Description:      Long toast message
     * Notes:            A proxy method to the Toaster class. This also takes care of using the Toaster's Singleton.
     */
    public static void msgLong(Context context, String msg) {
        Toaster.msgLong(context, msg);
    }

    /**
    * Description:      Short toast message
    * Notes:            A proxy method to the Toaster class. This also takes care of using the Toaster's Singleton.
    */
    public static void msgShort(Context context, String msg) {
        Toaster.msgShort(context, msg);
    }

    /**
    * Description:
    */
    public static void sendMsg(Context context, String msg) {
        Toaster.msgLong(context, msg);
    }

    /**
     * Checks if Network connectivity is available to download OpenCellID data
     * Requires:        android:name="android.permission.ACCESS_NETWORK_STATE"
     */
    public static Boolean isNetAvailable(Context context) {
        try {
            ConnectivityManager cM = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo wifiInfo =   cM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            NetworkInfo mobileInfo = cM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
            if (wifiInfo != null && mobileInfo != null) {
                return wifiInfo.isConnected() || mobileInfo.isConnected();
            }
        } catch (Exception e) {
           Log.e(TAG, mTAG + e.getMessage(), e);
        }
        return false;
    }

   /**
    * Description:      Requests Cell data from OpenCellID.org (OCID).
    *
    * Notes:
    *
    *       The free OCID API has a download limit of 1000 BTSs for each download.
    *       Thus we need to carefully select the area we choose to download and make sure it is
    *       centered on the current GPS location. (It is also possible to query the number of
    *       cells in a particular bounding box (bbox), and use that.)
    *
    *       The bbox is described in the OCID API here:
    *       http://wiki.opencellid.org/wiki/API#Getting_the_list_of_cells_in_a_specified_area
    *
    *       In an urban area, we could try to limit ourselves to an area radius of ~2 Km.
    *       The (GSM) Timing Advance is limiting us to 35 Km.
    *
    *       The OCID API payload:
    *
    *       required:   key=<apiKey>&BBOX=<latmin>,<lonmin>,<latmax>,<lonmax>
    *       optional:   &mcc=<mcc>&mnc=<mnc>&lac=<lac>&radio=<radio>
    *                   &limit=<limit>&offset=<offset>&format=<format>
    *
    *       Our API query is using:  (Lat1,Lon1, Lat2,Lon2, mcc,mnc,lac)
    *
    *  Issues:
    *
    *      [ ] A too restrictive payload leads to many missing BTS in area, but a too liberal
    *          payload would return many less relevant ones and would cause us to reach the
    *          OCID API 1000 BTS download limit much faster. The solution would be to make the
    *          BBOX smaller, but that in turn, would result in the loss of some more distant,
    *          but still available towers. Possibly making them appears as RED, even when they
    *          are neither fake nor IMSI-catchers. However, a more realistic BTS picture is
    *          more useful, especially when sharing that info across different devices using
    *          on different RAT and MNO.
    *
    *      [ ] We need a smarter way to handle the downloading of the BTS data. The OCID API
    *          allows for finding how many cells are contained in a query. We can the use this
    *          info to loop the max query size to get all those cells. The Query format is:
    *
    *          GET:        http://<WebServiceURL>/cell/getInAreaSize
    *
    *          The OCID API payload:
    *
    *          required:     key=<apiKey>&BBOX=<latmin>,<lonmin>,<latmax>,<lonmax>
    *          optional:     &mcc=<mcc>&mnc=<mnc>&lac=<lac>&radio=<radio>&format=<format>
    *
    *          result:       JSON:
    *                              {
    *                                count: 123
    *                              }
    *
    *      [x]  Q:  How is the BBOX actually calculated from the "radius"?
    *           A:  It's calculated as an inscribed circle to a square of 2*R on each side.
    *               See ./utils/GeoLocation.java
    *
    *  Dependencies:    GeoLocation.java
    *
    *  Used:
    * @param cell Current Cell Information
    *
    */
    public static void getOpenCellData(InjectionAppCompatActivity injectionActivity, Cell cell, char type) {
        getOpenCellData(injectionActivity, cell, type, null);
    }
    public static void getOpenCellData(InjectionAppCompatActivity injectionActivity, Cell cell, char type, final AimsicdService service) {
        if (Helpers.isNetAvailable(injectionActivity)) {
            if (!"NA".equals(CellTracker.OCID_API_KEY)) {
                double earthRadius = 6371.01;   // [Km]
                int radius = 2;                 // [Km]     // Use a 2 Km radius with center at GPS location.

                if (Double.doubleToRawLongBits(cell.getLat()) != 0 &&
                        Double.doubleToRawLongBits(cell.getLon()) != 0) {
                    //New GeoLocation object to find bounding Coordinates
                    GeoLocation currentLoc = GeoLocation.fromDegrees(cell.getLat(), cell.getLon());

                    //Calculate the Bounding Box Coordinates using an N Km "radius" //0=min, 1=max
                    GeoLocation[] boundingCoords = currentLoc.boundingCoordinates(radius, earthRadius);
                    String boundParameter;

                    //Request OpenCellID data for Bounding Coordinates (0 = min, 1 = max)
                    boundParameter = String.valueOf(boundingCoords[0].getLatitudeInDegrees()) + ","
                                   + String.valueOf(boundingCoords[0].getLongitudeInDegrees()) + ","
                                   + String.valueOf(boundingCoords[1].getLatitudeInDegrees()) + ","
                                   + String.valueOf(boundingCoords[1].getLongitudeInDegrees());

                    Log.i(TAG, mTAG + "OCID BBOX is set to: " + boundParameter + "  with radius " + radius + " Km.");

                    StringBuilder sb = new StringBuilder();
                    sb.append("http://www.opencellid.org/cell/getInArea?key=")
                            .append(CellTracker.OCID_API_KEY).append("&BBOX=")
                            .append(boundParameter);

                    Log.i(TAG, mTAG + "OCID MCC is set to: " + cell.getMCC());
                    if (cell.getMCC() != Integer.MAX_VALUE) {
                        sb.append("&mcc=").append(cell.getMCC());
                    }
                    Log.i(TAG, mTAG + "OCID MNC is set to: " + cell.getMNC());
                    if (cell.getMNC() != Integer.MAX_VALUE) {
                        sb.append("&mnc=").append(cell.getMNC());
                    }

                    sb.append("&format=csv");
                    new RequestTask(injectionActivity, type, new RequestTask.AsyncTaskCompleteListener() {
                        @Override
                        public void onAsyncTaskSucceeded() {
                            Log.i(TAG, mTAG + "RequestTask's OCID download was successful. Callback rechecking connected cell against database");
                            service.getCellTracker().compareLacAndOpenDb();
                        }

                        @Override
                        public void onAsyncTaskFailed(String result) { }
                    }).execute(sb.toString());
                }
            } else {
                Fragment myFragment = injectionActivity.getSupportFragmentManager().findFragmentByTag(String.valueOf(DrawerMenu.ID.MAIN.ALL_CURRENT_CELL_DETAILS));
                if (myFragment instanceof MapFragment) {
                    ((MapFragment) myFragment).setRefreshActionButtonState(false);
                }
                Helpers.sendMsg(injectionActivity, injectionActivity.getString(zz.aimsicd.lite.R.string.no_opencellid_key_detected));
            }
        } else {
            Fragment myFragment = injectionActivity.getSupportFragmentManager().findFragmentByTag(String.valueOf(DrawerMenu.ID.MAIN.ALL_CURRENT_CELL_DETAILS));
            if (myFragment instanceof MapFragment) {
                ((MapFragment) myFragment).setRefreshActionButtonState(false);
            }

            final AlertDialog.Builder builder = new AlertDialog.Builder(injectionActivity);
            builder.setTitle(R.string.no_network_connection_title)
                    .setMessage(R.string.no_network_connection_message);
            builder.create().show();
        }
    }

    /**
     * Return a String List representing response from invokeOemRilRequestRaw
     *
     * @param aob Byte array response from invokeOemRilRequestRaw
     */
    public static List<String> unpackByteListOfStrings(byte aob[]) {

        if (aob.length == 0) {
            // WARNING: This one is very chatty!
            Log.i(TAG, mTAG + "invokeOemRilRequestRaw: byte-list response Length = 0");
            return Collections.emptyList();
        }
        int lines = aob.length / CHARS_PER_LINE;
        String[] display = new String[lines];

        for (int i = 0; i < lines; i++) {
            int offset, byteCount;
            offset = i * CHARS_PER_LINE + 2;
            byteCount = 0;

            if (offset + byteCount >= aob.length) {
               Log.e(TAG, mTAG + "Unexpected EOF");
                break;
            }
            while (aob[offset + byteCount] != 0 && (byteCount < CHARS_PER_LINE)) {
                byteCount += 1;
                if (offset + byteCount >= aob.length) {
                   Log.e(TAG, mTAG + "Unexpected EOF");
                    break;
                }
            }
            display[i] = new String(aob, offset, byteCount).trim();
        }
        int newLength = display.length;
        while (newLength > 0 && TextUtils.isEmpty(display[newLength - 1])) {
            newLength -= 1;
        }
        return Arrays.asList(Arrays.copyOf(display, newLength));
    }

    public static String getSystemProp(Context context, String prop, String def) {
        String result = null;
        try {
            result = SystemPropertiesReflection.get(context, prop);
        } catch (IllegalArgumentException iae) {
           Log.e(TAG, mTAG + "Failed to get system property: " + prop, iae);
        }
        return result == null ? def : result;
    }

    public static String convertStreamToString(InputStream is) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        reader.close();
        return sb.toString();
    }

    /**
     * Description:     Deletes the entire database by removing internal SQLite DB file
     *

     *
     * Dependencies:    Used in AIMSICD.java
     *
     * Notes:           See Android developer info: http://tinyurl.com/psz8vmt
     *
     *              WARNING!
     *              This deletes the entire database, thus any subsequent DB access will FC app.
     *              Therefore we need to either restart app or run AIMSICDDbAdapter, to rebuild DB.
     *              See: #581
     *
     *              In addition, since SQLite is kept in memory during lifetime of App, and
     *              is using Journaling, we have to restart app in order to clear old data
     *              already in memory.
     *
     * @param pContext Context of Activity
     */
    public static void askAndDeleteDb(final Context pContext) {
        AlertDialog lAlertDialog = new AlertDialog.Builder(pContext)
                .setNegativeButton(zz.aimsicd.lite.R.string.open_cell_id_button_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                })
                .setPositiveButton(R.string.open_cell_id_button_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Probably put in try/catch in case file removal fails...
                        pContext.stopService(new Intent(pContext, AimsicdService.class));
                        pContext.deleteDatabase("aimsicd.db");
                        new AIMSICDDbAdapter(pContext);
                        pContext.startService(new Intent(pContext, AimsicdService.class));
                        msgLong(pContext, pContext.getString(R.string.delete_database_msg_success));
                    }
                })
                .setMessage(pContext.getString(R.string.clear_database_question))
                .setTitle(R.string.clear_database)
                .setCancelable(false)
                .setIcon(R.drawable.ic_action_delete_database).create();
        lAlertDialog.show();
    }
} public class Icon {

    public enum Type {

        // FLAT Icon-Style
        FLAT(
                new Function<Status, Integer>() {
                    @NonNull
                    @Override
                    public Integer apply(Status status) {
                        switch (status) {
                            case IDLE:
                                return R.drawable.flat_idle;
                            case OK:
                                return R.drawable.flat_ok;
                            case MEDIUM:
                                return R.drawable.flat_medium;
                            case HIGH:
                                return R.drawable.flat_high;
                            case DANGER:
                                return R.drawable.flat_danger;
                            case SKULL:
                                return R.drawable.flat_skull;
                            default:
                                return R.drawable.flat_idle;
                        }
                    }
                }
        ),

         // SENSE Icon-Style
        SENSE(
                new Function<Status, Integer>() {
                    @NonNull
                    @Override
                    public Integer apply(@Nullable Status status) {
                        switch (status) {
                            case IDLE:
                                return R.drawable.sense_idle;
                            case OK:
                                return R.drawable.sense_ok;
                            case MEDIUM:
                                return R.drawable.sense_medium;
                            case HIGH:
                                return R.drawable.sense_high;
                            case DANGER:
                                return R.drawable.sense_danger;
                            case SKULL:
                                return R.drawable.sense_skull;
                            default:
                                return R.drawable.sense_idle;
                        }
                    }
                }
        ),

        // WHITE Icon-Style
        WHITE(
                new Function<Status, Integer>() {
                    @NonNull
                    @Override
                    public Integer apply(@Nullable Status status) {
                        switch (status) {
                            case IDLE:
                                return R.drawable.white_idle;
                            case OK:
                                return R.drawable.white_ok;
                            case MEDIUM:
                                return R.drawable.white_medium;
                            case HIGH:
                                return R.drawable.white_high;
                            case DANGER:
                                return R.drawable.white_danger;
                            case SKULL:
                                return R.drawable.white_skull;
                            default:
                                return R.drawable.white_idle;
                        }
                    }
                }
        );

        Function<Status, Integer> iconMapper;

        Type(Function<Status, Integer> iconMapper) {
            this.iconMapper = iconMapper;
        }
    }

    /**
     * Returns a icon of the Type $t, what kind of icon is returned is decided
     * from what the current status is.
     */
    @DrawableRes
    public static int getIcon(Type type, Status status) {
        return type.iconMapper.apply(status);
    }
} public class LocationServices {

    private static void writeData(OutputStream out, int cid, int lac, int mnc, int mcc)
            throws IOException {
        DataOutputStream dataOutputStream = new DataOutputStream(out);
        dataOutputStream.writeShort(0x0E); // Fct code

        dataOutputStream.writeInt(0); // requesting 8 byte session
        dataOutputStream.writeInt(0);

        dataOutputStream.writeShort(0); // country code string
        dataOutputStream.writeShort(0); // client descriptor string
        dataOutputStream.writeShort(0); // version tag string

        dataOutputStream.writeByte(0x1B); // Fct code

        dataOutputStream.writeInt(0); // MNC?
        dataOutputStream.writeInt(0); // MCC?
        dataOutputStream.writeInt(3); // RAT  = Radio Access Type (3=GSM, 5=UMTS)

        dataOutputStream.writeShort(0); // length of provider name

        // provider name string
        dataOutputStream.writeInt(cid); // CID
        dataOutputStream.writeInt(lac); // LAC
        dataOutputStream.writeInt(mnc); // MNC
        dataOutputStream.writeInt(mcc); // MCC
        dataOutputStream.writeInt(-1);  // always -1
        dataOutputStream.writeInt(0);   // rx level

        dataOutputStream.flush();
    }

    public static class LocationAsync extends AsyncTask<Integer, Void, float[]> {
        public AsyncResponse delegate = null;

        @Override
        protected float[] doInBackground(Integer... params) {
            /*try {
                String mmapUrl = "http://www.google.com/glm/mmap";

                URL url = new URL(mmapUrl);
                HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
                httpConn.setRequestMethod("POST");
                httpConn.setDoOutput(true);
                httpConn.connect();

                OutputStream outputStream = httpConn.getOutputStream();
                writeData(outputStream,
                        params[0], //CID
                        params[1], //LAC
                        params[2], //MNC
                        params[3]); //MCC

                InputStream inputStream = httpConn.getInputStream();
                DataInputStream dataInputStream = new DataInputStream(inputStream);

                dataInputStream.readShort();
                dataInputStream.readByte();

                int code = dataInputStream.readInt();
                if (code == 0) {
                    float latitude = dataInputStream.readInt() / 1000000f;
                    float longitude = dataInputStream.readInt() / 1000000f;
                    return new float[] {latitude, longitude};
                }

            } catch (IOException e) {
                e.printStackTrace();
            }*/

            return new float[] {0.0f, 0.0f};
        }

        @Override
        protected void onPostExecute(float[] floats) {
            delegate.processFinish(floats);
        }
    }
} public class RequestTask extends BaseAsyncTask<String, Integer, String> {

    public static final String TAG = "AICDL";
    public static final String mTAG = "XXX";

    //Calling from the menu more extensive(more difficult for sever),
    // we have to give more time for the server response
    public static final int REQUEST_TIMEOUT_MAPS = 80000;       // [ms] 80 s Calling from map
    public static final int REQUEST_TIMEOUT_MENU = 80000;       // [ms] 80 s Calling from menu

    public static final char DBE_DOWNLOAD_REQUEST = 1;          // OCID download request from "APPLICATION" drawer title
    public static final char DBE_DOWNLOAD_REQUEST_FROM_MAP = 2; // OCID download request from "Antenna Map Viewer"
    public static final char DBE_UPLOAD_REQUEST = 6;            // OCID upload request from "APPLICATION" drawer title
    public static final char BACKUP_DATABASE = 3;               // Backup DB to CSV and AIMSICD_dump.db
    public static final char RESTORE_DATABASE = 4;              // Restore DB from CSV files
    public static final char CELL_LOOKUP = 5;                   // TODO: "All Current Cell Details (ALL_CURRENT_CELL_DETAILS)"


    private AIMSICDDbAdapter mDbAdapter;
    private Context mAppContext;
    private char mType;
    private int mTimeOut;

    private AsyncTaskCompleteListener mListener;

    @Inject
    private OkHttpClient okHttpClient;

    /**
     *
     * @param context App context
     * @param type What type of request to be performed (download OCID, upload OCID, DB backup, etc.)
     * @param listener Allows the caller of RequestTask to implement success/fail callbacks
     */
    public RequestTask(InjectionAppCompatActivity context, char type, AsyncTaskCompleteListener listener) {
        super(context);
        this.mType = type;
        this.mAppContext = context.getApplicationContext();
        this.mDbAdapter = new AIMSICDDbAdapter(mAppContext);
        this.mTimeOut = REQUEST_TIMEOUT_MAPS;
        this.mListener = listener;
    }

    /**
     * @deprecated Use {@link #RequestTask(InjectionAppCompatActivity, char, AsyncTaskCompleteListener)}
     *             instead because of the listener callback interface.
     * @param context
     * @param type
     */
    @Deprecated
    public RequestTask(InjectionAppCompatActivity context, char type) {
        this(context, type, null);
       Log.w(TAG, mTAG + "RequestTask(InjectionAppCompatActivity, char) is deprecated in favour of using listener callbacks");
    }

    @Override
    protected String doInBackground(String... commandString) {

        // We need to create a separate case for UPLOADING to DBe (OCID, MLS etc)
        switch (mType) {
            // OCID upload request from "APPLICATION" drawer title
            case DBE_UPLOAD_REQUEST:
                try {
                    boolean prepared = mDbAdapter.prepareOpenCellUploadData();

                    Log.i(TAG, mTAG + "OCID upload data prepared - " + String.valueOf(prepared));
                    if (prepared) {
                        File file = new File((mAppContext.getExternalFilesDir(null) + File.separator) + "OpenCellID/aimsicd-ocid-data.csv");
                        publishProgress(25, 100);

                        RequestBody requestBody = new MultipartBuilder()
                                .type(MultipartBuilder.FORM)
                                .addFormDataPart("key", CellTracker.OCID_API_KEY)
                                .addFormDataPart("datafile", "aimsicd-ocid-data.csv", RequestBody.create(MediaType.parse("text/csv"), file))
                                .build();

                        Request request = new Request.Builder()
                                .url("http://www.opencellid.org/measure/uploadCsv")
                                .post(requestBody)
                                .build();

                        publishProgress(60, 100);

                        Response response = okHttpClient.newCall(request).execute();

                        publishProgress(80, 100);
                        if (response != null) {
                            Log.i(TAG, mTAG + "OCID Upload Response: "
                                    + response.code() + " - "
                                    + response.message());
                            if (response.code() == 200) {
                                mDbAdapter.ocidProcessed();
                            }
                            publishProgress(95, 100);
                        }
                        return "Successful";
                    } else {
                        Helpers.msgLong(mAppContext, mAppContext.getString(R.string.no_data_for_publishing));
                        return null;
                    }

                    // all caused by httpclient.execute(httppost);
                } catch (UnsupportedEncodingException e) {
                   Log.e(TAG, mTAG + "Upload OpenCellID data Exception", e);
                } catch (FileNotFoundException e) {
                   Log.e(TAG, mTAG + "Upload OpenCellID data Exception", e);
                } catch (IOException e) {
                   Log.e(TAG, mTAG + "Upload OpenCellID data Exception", e);
                } catch (Exception e) {
                   Log.e(TAG, mTAG + "Upload OpenCellID data Exception", e);
                }

                // DOWNLOADING...
            case DBE_DOWNLOAD_REQUEST:          // OCID download request from "APPLICATION" drawer title
                mTimeOut = REQUEST_TIMEOUT_MENU;
            case DBE_DOWNLOAD_REQUEST_FROM_MAP: // OCID download request from "Antenna Map Viewer"
                int count;
                try {
                    long total;
                    int progress = 0;
                    String dirName = getOCDBDownloadDirectoryPath(mAppContext);
                    File dir = new File(dirName);
                    if (!dir.exists()) {
                        dir.mkdirs();
                    }
                    File file = new File(dir, OCDB_File_Name);
                    Log.i(TAG, mTAG + "DBE_DOWNLOAD_REQUEST write to: " + dirName + OCDB_File_Name);

                    Request request = new Request.Builder()
                            .url(commandString[0])
                            .get()
                            .build();

                    Response response;
                    try {
                        // OCID's API can be slow. Give it up to a minute to do its job. Since this
                        // is a backgrounded task, it's ok to wait for a while.
                        okHttpClient.setReadTimeout(60, TimeUnit.SECONDS);
                        response = okHttpClient.newCall(request).execute();
                        okHttpClient.setReadTimeout(10, TimeUnit.SECONDS); // Restore back to default
                    } catch (SocketTimeoutException e) {
                       Log.w(TAG, mTAG + "Trying to talk to OCID timed out after 60 seconds. API is slammed? Throttled?");
                        return "Timeout";
                    }

                    if (response.code() != 200) {
                        try {
                            String error = response.body().string();
                            Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.download_error) + " " + error);
                           Log.e(TAG, mTAG + "Download OCID data error: " + error);
                        } catch (Exception e) {
                            Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_error) + " "
                                    + e.getClass().getName() + " - "
                                    + e.getMessage());
                           Log.e(TAG, mTAG + "Download OCID exception: ", e);
                        }
                        return "Error";
                    } else {
                        // This returns "-1" for streamed response (Chunked Transfer Encoding)
                        total = response.body().contentLength();
                        if (total == -1) {
                           Log.d(TAG, mTAG + "doInBackground DBE_DOWNLOAD_REQUEST total not returned!");
                            total = 1024; // Let's set it arbitrarily to something other than "-1"
                        } else {
                           Log.d(TAG, mTAG + "doInBackground DBE_DOWNLOAD_REQUEST total: " + total);
                            publishProgress((int) (0.25 * total), (int) total); // Let's show something!
                        }

                        FileOutputStream output = new FileOutputStream(file, false);
                        InputStream input = new BufferedInputStream(response.body().byteStream());

                        byte[] data = new byte[1024];
                        while ((count = input.read(data)) > 0) {
                            // writing data to file
                            output.write(data, 0, count);
                            progress += count;
                            publishProgress(progress, (int) total);
                        }
                        input.close();
                        // flushing output
                        output.flush();
                        output.close();
                    }
                    return "Successful";

                } catch (IOException e) {
                   Log.w(TAG, mTAG + "Problem reading data from steam", e);
                    return null;
                }

            case BACKUP_DATABASE:
                if (mDbAdapter.backupDB()) {
                    return "Successful";
                }
                return null;

            case RESTORE_DATABASE:
                if (mDbAdapter.restoreDB()) {
                    return "Successful";
                }
                return null;
        }

        return null;
    }

    /**
     *  Description:    This is where we:
     *
     *                  1) Check the success for OCID data download
     *                  2) call the updateOpenCellID() to populate the DBe_import table
     *                  3) call the checkDBe() to cleanup bad cells from imported data
     *                  4) present a failure/success toast message
     *                  5) set a shared preference to indicate that data has been downloaded:
     *                      "ocid_downloaded true"
     *
     *  Issues:
     *                  [ ] checkDBe() is incomplete, due to missing RAT column in DBe_import
     *
     */
    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        TinyDB tinydb = TinyDB.getInstance();

        switch (mType) {
            case DBE_DOWNLOAD_REQUEST:
                // if `result` is null, it will evaluate to false, no need to check for null
                if ("Successful".equals(result)) {

                    if (mDbAdapter.populateDBeImport()) {
                        Helpers.msgShort(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.opencellid_data_successfully_received));
                    }

                    mDbAdapter.checkDBe();
                    tinydb.putBoolean("ocid_downloaded", true);
                } else if ("Timeout".equals(result)) {
                    Helpers.msgLong(mAppContext, mAppContext.getString(R.string.download_timed_out));
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.error_retrieving_opencellid_data));
                }
                break;

            case DBE_DOWNLOAD_REQUEST_FROM_MAP:
                if ("Successful".equals(result)) {
                    if (mDbAdapter.populateDBeImport()) {
                        Intent intent = new Intent(MapFragment.updateOpenCellIDMarkers);
                        LocalBroadcastManager.getInstance(mAppContext).sendBroadcast(intent);
                        Helpers.msgShort(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.opencellid_data_successfully_received_markers_updated));

                        mDbAdapter.checkDBe();
                        tinydb.putBoolean("ocid_downloaded", true);
                    }
                } else if ("Timeout".equals(result)) {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.download_timed_out));
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.error_retrieving_opencellid_data));
                }
                showHideMapProgressBar(false);
                TinyDB.getInstance().putBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP, true);
                break;

            case DBE_UPLOAD_REQUEST:
                if ("Successful".equals(result)) {
                    Helpers.msgShort(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.uploaded_bts_data_successfully));
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.error_uploading_bts_data));
                }
                break;

            case RESTORE_DATABASE:
                if ("Successful".equals(result)) {
                    Helpers.msgShort(mAppContext, mAppContext.getString(R.string.restore_database_completed));
                    Activity lActivity = getActivity();

                    //Activity may be detached or destroyed
                    if (lActivity != null) {
                        final AlertDialog.Builder builder = new AlertDialog.Builder(lActivity);
                        builder.setTitle(R.string.restore_database_completed_title).setMessage(
                                lActivity.getString(R.string.restore_database_completed));
                        builder.create().show();
                    }
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.error_restoring_database));
                }
                break;

            case BACKUP_DATABASE:
                if ("Successful".equals(result)) {

                    // strings.xml: pref_last_db_backup_version
                    //tinydb.putInt(mContext.getString(R.string.pref_last_database_backup_version), AIMSICDDbAdapter.DATABASE_VERSION); //TODO
                    tinydb.putInt("pref_last_db_backup_version", AIMSICDDbAdapter.DATABASE_VERSION);
                    Activity lActivity = getActivity();

                    //Activity may be detached or destroyed
                    if (lActivity != null) {
                        final AlertDialog.Builder builder = new AlertDialog.Builder(lActivity);
                        builder.setTitle(R.string.database_export_successful).setMessage(
                                lActivity.getString(zz.aimsicd.lite.R.string.database_backup_successfully_saved_to) + "\n" + AIMSICDDbAdapter.mExternalFilesDirPath);
                        builder.create().show();
                    }
                } else {
                    Helpers.msgLong(mAppContext, mAppContext.getString(zz.aimsicd.lite.R.string.error_backing_up_data));
                }
        }
        if (mListener != null) {
            if ("Successful".equals(result)) {
                mListener.onAsyncTaskSucceeded();
            } else {
                mListener.onAsyncTaskFailed(result);
            }
        }
    }

    @Override
    protected void onActivityDetached() {
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(false);
        }
    }

    @Override
    protected void onActivityAttached() {
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(true);
        }
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
        if (mType == DBE_DOWNLOAD_REQUEST_FROM_MAP) {
            showHideMapProgressBar(false);
        }
    }

    private void showHideMapProgressBar(boolean pFlag) {
        InjectionAppCompatActivity lActivity = getActivity();
        if (BuildConfig.DEBUG && lActivity == null) {
            Log.i(TAG, mTAG + "BaseTask showHideMapProgressBar() activity is null");
        }

        if (lActivity != null) {
            Fragment myFragment = lActivity.getSupportFragmentManager().findFragmentByTag(String.valueOf(DrawerMenu.ID.MAIN.ALL_CURRENT_CELL_DETAILS));
            if (myFragment instanceof MapFragment) {
                ((MapFragment) myFragment).setRefreshActionButtonState(pFlag);
            }
        }
    }

    public static final String OCDB_File_Name = "opencellid.csv";

    /**
     * The folder path to OCDB download.
     * @param context
     * @return
     */
    public static String getOCDBDownloadDirectoryPath(Context context) {
        return (context.getExternalFilesDir(null) + File.separator) + "OpenCellID/";
    }

    /**
     * The interface to be implemented by the caller of RequestTask so it can perform contextual
     * actions once the async task is completed.
     *
     * E.g. rechecking current cell in the newly updated database after OCID download.
     */
    public interface AsyncTaskCompleteListener {
        void onAsyncTaskSucceeded();
        void onAsyncTaskFailed(String result);
    }
}
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.List;
import java.util.Locale;
import zz.aimsicd.lite.adapters.AIMSICDDbAdapter;
import zz.aimsicd.lite.constants.DrawerMenu;
import zz.aimsicd.lite.service.AimsicdService;
import zz.aimsicd.lite.service.CellTracker;
import zz.aimsicd.lite.ui.activities.AboutActivity;
import zz.aimsicd.lite.ui.activities.BaseActivity;
import zz.aimsicd.lite.ui.activities.DebugLogs;
import zz.aimsicd.lite.ui.activities.SettingsActivity;
import zz.aimsicd.lite.ui.drawer.DrawerMenuActivityConfiguration;
import zz.aimsicd.lite.ui.drawer.NavDrawerItem;
import zz.aimsicd.lite.ui.fragments.AtCommandFragment;
import zz.aimsicd.lite.ui.fragments.CellInfoFragment;
import zz.aimsicd.lite.ui.fragments.DbViewerFragment;
import zz.aimsicd.lite.ui.fragments.DeviceFragment;
import zz.aimsicd.lite.ui.fragments.MapFragment;
import zz.aimsicd.lite.utils.AsyncResponse;
import zz.aimsicd.lite.utils.Cell;
import zz.aimsicd.lite.utils.GeoLocation;
import zz.aimsicd.lite.utils.Helpers;
import zz.aimsicd.lite.utils.Icon;
import zz.aimsicd.lite.utils.LocationServices;
import zz.aimsicd.lite.utils.RequestTask;
/* Android IMSI-Catcher Detector | (c) AIMSICD Privacy Project
 * -----------------------------------------------------------
 * LICENSE:  http://git.io/vki47 | TERMS:  http://git.io/vki4o
 * -----------------------------------------------------------
 */
package zz.aimsicd.lite;




public class MainActivity extends BaseActivity implements AsyncResponse {

    private boolean mBound;
    private SharedPreferences prefs;
    private SharedPreferences.OnSharedPreferenceChangeListener prefListener;
    private Editor prefsEditor;
    private String mDisclaimerAccepted;
    private AimsicdService mAimsicdService;