public class AppAIMSICD extends InjectionApplication {

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

    private static WeakReference<AppAIMSICD> instance;

    public static AppAIMSICD getInstance() {
        return instance.get();
    }

    private Status currentStatus;


    /**
     * Maps between an activity class name and the list of currently running
     * AsyncTasks that were spawned while it was active.
     */
    private SparseArray<List<BaseAsyncTask<?, ?, ?>>> mActivityTaskMap;

    public AppAIMSICD() {
        mActivityTaskMap = new SparseArray<>();
    }

    @Override
    public void onCreate() {
        instance = new WeakReference<>(this);
        //addModule(new AndroidLoggerModule());
        //addModule(OkHttpModule.withCache(this));
        super.onCreate();
        TinyDB.getInstance().init(getApplicationContext());
        TinyDB.getInstance().putBoolean(TinyDbKeys.FINISHED_LOAD_IN_MAP, true);
    }

    public void removeTask(BaseAsyncTask<?, ?, ?> pTask) {
        int key;
        for (int i = 0; i < mActivityTaskMap.size(); i++) {
            key = mActivityTaskMap.keyAt(i);
            List<BaseAsyncTask<?, ?, ?>> tasks = mActivityTaskMap.get(key);
            for (BaseAsyncTask<?, ?, ?> lTask : tasks) {
                if (lTask.equals(pTask)) {
                    tasks.remove(lTask);
                    Log.i(TAG, mTAG + "BaseTask removed:" + pTask.toString());

                    break;
                }
            }
            if (tasks.size() == 0) {
                mActivityTaskMap.remove(key);
                return;
            }
        }
    }

    public void addTask(Activity activity, BaseAsyncTask<?, ?, ?> pTask) {
        if (activity == null) {
            return;
        }

       Log.d(TAG, mTAG + "BaseTask addTask activity:" + activity.getClass().getCanonicalName());

        int key = activity.getClass().getCanonicalName().hashCode();
        List<BaseAsyncTask<?, ?, ?>> tasks = mActivityTaskMap.get(key);
        if (tasks == null) {
            tasks = new ArrayList<>();
            mActivityTaskMap.put(key, tasks);
        }
        Log.i(TAG, mTAG + "BaseTask added:" + pTask.toString());
        tasks.add(pTask);
    }

    public void detach(Activity activity) {
        if (activity == null) {
            return;
        }

       Log.d(TAG, mTAG + "BaseTask detach:" + activity.getClass().getCanonicalName());

        List<BaseAsyncTask<?, ?, ?>> tasks = mActivityTaskMap.get(activity.getClass().getCanonicalName().hashCode());
        if (tasks != null) {
            for (BaseAsyncTask<?, ?, ?> task : tasks) {
                task.setActivity(null);
            }
        }
    }

    public void attach(InjectionAppCompatActivity activity) {
        if (activity == null) {
            return;
        }
       Log.d(TAG, mTAG + "BaseTask attach:" + activity.getClass().getCanonicalName());

        List<BaseAsyncTask<?, ?, ?>> tasks = mActivityTaskMap.get(activity.getClass().getCanonicalName().hashCode());
        if (tasks != null) {
            for (BaseAsyncTask<?, ?, ?> task : tasks) {
                task.setActivity(activity);
            }
        }
    }

    /**
     * Changes the current status, this will also trigger a local broadcast event
     * if the new status is different from the previous one
     */
    public void setCurrentStatus(Status status, boolean vibrate, int minVibrateLevel) {
        if (status == null) {
            status = Status.IDLE;
        }
        if (status != currentStatus) {
            Intent intent = new Intent("StatusChange");
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

            if (vibrate && status.ordinal() >= minVibrateLevel) {
                ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(100);
            }
        }
        currentStatus = status;
    }

    /**
     * Returns the current status
     */
    public Status getStatus() {
        if (currentStatus == null) {
            return Status.IDLE;
        }
        return currentStatus;
    }
} 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;

    private DrawerLayout mDrawerLayout;
    private ActionBar mActionBar;
    private ListView mDrawerList;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;

    private DeviceFragment deviceFragment;
    private CellInfoFragment cellInfoFragment;
    private AtCommandFragment atCommandFragment;
    private DbViewerFragment dbViewerFragment;
    private MapFragment mapFragment;

    private long mLastPress = 0;    // Back press to exit timer

    private DrawerMenuActivityConfiguration mNavConf;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        deviceFragment = new DeviceFragment();
        cellInfoFragment = new CellInfoFragment();
        atCommandFragment = new AtCommandFragment();
        dbViewerFragment = new DbViewerFragment();
        mapFragment = new MapFragment();

        mNavConf = new DrawerMenuActivityConfiguration.Builder(this).build();

        mDrawerLayout = (DrawerLayout) findViewById(mNavConf.getDrawerLayoutId());
        mDrawerList = (ListView) findViewById(mNavConf.getLeftDrawerId());
        mActionBar = getSupportActionBar();
        mTitle =  getTitle();
        mDrawerTitle = getTitle();

        mDrawerList.setAdapter(mNavConf.getBaseAdapter());

        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
        ) {
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                mActionBar.setTitle(mTitle);
                invalidateOptionsMenu();
            }

            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                mActionBar.setTitle(mDrawerTitle);
                invalidateOptionsMenu();
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mActionBar.setHomeButtonEnabled(true);

        prefs = getSharedPreferences(AimsicdService.SHARED_PREFERENCES_BASENAME, 0);

                /* Pref listener to enable sms detection on pref change   */
        prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
            public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
                if (key.equals(getString(R.string.adv_user_root_pref_key))) {
                    SmsDetection();
                }

            }
        };
        prefs.registerOnSharedPreferenceChangeListener(prefListener);

        mDisclaimerAccepted = getResources().getString(R.string.disclaimer_accepted);

        if (!prefs.getBoolean(mDisclaimerAccepted, false)) {
            final AlertDialog.Builder disclaimer = new AlertDialog.Builder(this)
                    .setTitle(R.string.disclaimer_title)
                    .setMessage(R.string.disclaimer)
                    .setPositiveButton(R.string.text_agree, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            prefsEditor = prefs.edit();
                            prefsEditor.putBoolean(mDisclaimerAccepted, true);
                            prefsEditor.apply();
                            startService();
                        }
                    })
                    .setNegativeButton(R.string.text_disagree, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            prefsEditor = prefs.edit();
                            prefsEditor.putBoolean(mDisclaimerAccepted, false);
                            prefsEditor.apply();
                            Uri packageUri = Uri.parse("package:zz.aimsicd.lite");
                            Intent uninstallIntent =
                                    new Intent(Intent.ACTION_DELETE, packageUri);
                            startActivity(uninstallIntent);
                            finish();
                            if (mAimsicdService != null) {
                                mAimsicdService.onDestroy();
                            }
                        }
                    });

            AlertDialog disclaimerAlert = disclaimer.create();
            disclaimerAlert.show();
        } else {
            startService();
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        final String iconType = prefs.getString(getString(R.string.pref_ui_icons_key), "SENSE").toUpperCase(Locale.US);
        mActionBar.setIcon(Icon.getIcon(Icon.Type.valueOf(iconType), ((AppAIMSICD) getApplication()).getStatus()));
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }


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

        final String PERSIST_SERVICE = getString(R.string.pref_persistservice_key);
        boolean persistService = prefs.getBoolean(PERSIST_SERVICE, false);
        if (!persistService) {
            stopService(new Intent(this, AimsicdService.class));
        }
    }

    private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            mDrawerLayout.closeDrawer(mDrawerList);
            selectDrawerItem(position);
        }
    }

    void selectDrawerItem(int position) {
        NavDrawerItem selectedItem = mNavConf.getNavItems().get(position);
        String title = selectedItem.getLabel();

        switch (selectedItem.getId()) {
            case DrawerMenu.ID.MAIN.PHONE_SIM_DETAILS:
                openFragment(deviceFragment);
                title = getString(R.string.app_name_short);
                break;
            case DrawerMenu.ID.MAIN.CURRENT_TREAT_LEVEL:
                openFragment(cellInfoFragment);
                title = getString(R.string.app_name_short);
                break;
            case DrawerMenu.ID.MAIN.AT_COMMAND_INTERFACE:
                openFragment(atCommandFragment);
                title = getString(R.string.app_name_short);
                break;
            case DrawerMenu.ID.MAIN.DB_VIEWER:
                openFragment(dbViewerFragment);
                title = getString(R.string.app_name_short);
                break;
            case DrawerMenu.ID.APPLICATION.UPLOAD_LOCAL_BTS_DATA:
                // Request uploading here?
                new RequestTask(this, RequestTask.DBE_UPLOAD_REQUEST).execute("");
                // no string needed for csv based upload
                break;
            case DrawerMenu.ID.MAIN.ANTENNA_MAP_VIEW:
                openFragment(mapFragment);
                title = getString(R.string.app_name_short);
                break;
            case DrawerMenu.ID.DATABASE_SETTINGS.BACKUP_DB:
                new RequestTask(this, RequestTask.BACKUP_DATABASE).execute();
                break;
        }

        if (selectedItem.getId() == DrawerMenu.ID.DATABASE_SETTINGS.RESTORE_DB) {
            if (CellTracker.LAST_DB_BACKUP_VERSION < AIMSICDDbAdapter.DATABASE_VERSION) {
                Helpers.msgLong(this, getString(R.string.unable_to_restore_backup_from_previous_database_version));
            } else {
                new RequestTask(this, RequestTask.RESTORE_DATABASE).execute();
            }
        } else if (selectedItem.getId() == DrawerMenu.ID.DATABASE_SETTINGS.RESET_DB) {
            // 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: https://github.com/SecUpwN/Android-IMSI-Catcher-Detector/issues/581 and Helpers.java
            Helpers.askAndDeleteDb(this);


        } else if (selectedItem.getId() == DrawerMenu.ID.APPLICATION.DOWNLOAD_LOCAL_BTS_DATA) {
            downloadBtsDataIfApiKeyAvailable();
        } else if (selectedItem.getId() == DrawerMenu.ID.APPLICATION.QUIT) {
            try {
                if (mAimsicdService.isSmsTracking()) {
                    mAimsicdService.stopSmsTracking();
                }
            } catch (Exception ee) {
               Log.w(TAG, mTAG + "Exception in smstracking module: " + ee.getMessage());
            }

            if (mAimsicdService != null) {
                mAimsicdService.onDestroy();
            }
            //Close database on Exit
            Log.i(TAG, mTAG + "Closing db from DrawerMenu.ID.APPLICATION.QUIT");
            new AIMSICDDbAdapter(getApplicationContext()).close();
            finish();
        }

        mDrawerList.setItemChecked(position, true);

        if (selectedItem.updateActionBarTitle()) {
            setTitle(title);
        }

        if (this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) {
            mDrawerLayout.closeDrawer(mDrawerList);
        }
    }

    private void openFragment(Fragment fragment) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        transaction.replace(R.id.content_frame, fragment);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    private void downloadBtsDataIfApiKeyAvailable() {
        if (CellTracker.OCID_API_KEY != null && !CellTracker.OCID_API_KEY.equals("NA")) {

            Cell cell = new Cell();
            TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
            String networkOperator = tm.getNetworkOperator();

            if (networkOperator != null && !networkOperator.isEmpty()) {
                int mcc = Integer.parseInt(networkOperator.substring(0, 3));
                cell.setMCC(mcc);
                int mnc = Integer.parseInt(networkOperator.substring(3));
                cell.setMNC(mnc);
               Log.d(TAG, mTAG + "CELL:: mcc=" + mcc + " mnc=" + mnc);
            }


            GeoLocation loc = mAimsicdService.lastKnownLocation();
            if (loc != null) {
                Helpers.msgLong(this, getString(R.string.contacting_opencellid_for_data));

                cell.setLon(loc.getLongitudeInDegrees());
                cell.setLat(loc.getLatitudeInDegrees());
                Helpers.getOpenCellData(this, cell, RequestTask.DBE_DOWNLOAD_REQUEST, mAimsicdService);
            } else {
                Helpers.msgShort(this, getString(R.string.waiting_for_location));

                // This uses the LocationServices to get CID/LAC/MNC/MCC to be used
                // for grabbing the BTS data from OCID, via their API.
                // CID Location Async Output Delegate Interface Implementation
                LocationServices.LocationAsync locationAsync
                        = new LocationServices.LocationAsync();
                locationAsync.delegate = this;
                locationAsync.execute(
                        mAimsicdService.getCell().getCID(),
                        mAimsicdService.getCell().getLAC(),
                        mAimsicdService.getCell().getMNC(),
                        mAimsicdService.getCell().getMCC());
            }
        } else {
            Helpers.sendMsg(this, getString(R.string.no_opencellid_key_detected));
        }
    }

    @Override
    public void processFinish(float[] location) {
        Log.i(TAG, mTAG + "processFinish - location[0]=" + location[0] + " location[1]=" + location[1]);


        if (Float.floatToRawIntBits(location[0]) == 0
                && Float.floatToRawIntBits(location[1]) != 0) {
            Helpers.msgLong(this, getString(R.string.contacting_opencellid_for_data));
            Helpers.getOpenCellData(this, mAimsicdService.getCell(), RequestTask.DBE_DOWNLOAD_REQUEST, mAimsicdService);
        } else {
            Helpers.msgLong(this, getString(R.string.unable_to_determine_last_location));
        }
    }

    @Override
    public void processFinish(List<Cell> cells) {
        if (cells != null) {
            if (!cells.isEmpty()) {
                for (Cell cell : cells) {
                    Log.i(TAG, mTAG + "processFinish - Cell =" + cell.toString());
                    if (cell.isValid()) {
                        mAimsicdService.setCell(cell);
                        Intent intent = new Intent(AimsicdService.UPDATE_DISPLAY);
                        intent.putExtra("update", true);
                        sendBroadcast(intent);
                    }
                }
            }
        }
    }


    @Override
    public void setTitle(CharSequence title) {
        mTitle = title;
        mActionBar.setTitle(mTitle);
    }

    /**
     * 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;

            // Check if tracking cell details check location services are still enabled
            if (mAimsicdService.isTrackingCell()) {
                mAimsicdService.checkLocationServices();
            }

            if (!mAimsicdService.isSmsTracking() && prefs.getBoolean(getString(R.string.adv_user_root_pref_key), false)) {
                    /*Auto Start sms detection here if:
                    *    isSmsTracking = false <---- not running
                    *    root sms enabled = true
                    *
                    * */
                SmsDetection();
            }
        }

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

    private void startService() {
        // don't start service if disclaimer is not accepted
        if (!prefs.getBoolean(mDisclaimerAccepted, false)) {
            return;
        }

        if (!mBound) {
            // Bind to LocalService
            Intent intent = new Intent(MainActivity.this, AimsicdService.class);
            //Start Service before binding to keep it resident when activity is destroyed
            startService(intent);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

            openFragment(deviceFragment);
        }
    }

    /**
     * Triggered when GUI is opened
     */
    @Override
    public void onResume() {
        super.onResume();
        invalidateOptionsMenu();
        startService();
    }


    /**
     * Triggered when GUI is closed/put to background
     */
    @Override
    public void onPause() {
        super.onPause();
    }

    /**
     * Invoked only once during app's lifetime. For successive calls see
     * {@link #onPrepareOptionsMenu(Menu)}. Both used primarily to determine checkboxes for tracking
     * and detection.
     *
     * Because the service may not available on app boot, we default to false until the service
     * boots. Once that occurs, the checkboxes are driven by the service's status.
     *
     * @param menu
     * @return
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main_menu, menu);

        MenuItem toggleAttackDetectionMenuItem = menu.findItem(R.id.toggle_attack_detection);
        MenuItem toggleCellTrackingMenuItem = menu.findItem(R.id.toggle_cell_tracking);

        // The service may not exist on first app boot. Choose sane defaults
        if (mAimsicdService == null) {
            toggleAttackDetectionMenuItem.setChecked(false);
            toggleCellTrackingMenuItem.setChecked(false);
        } else {
            toggleAttackDetectionMenuItem.setChecked(mAimsicdService.isMonitoringCell());
            toggleCellTrackingMenuItem.setChecked(mAimsicdService.isTrackingCell());
        }
        return true;
    }

    /**
     * Display latest cell tracking & attack detection truth when opening menu over & over
     *
     * @param menu
     * @return
     */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // Bail early if there's no service to get the facts from
        if (mAimsicdService == null) {
            return super.onPrepareOptionsMenu(menu);
        }
        MenuItem toggleAttackDetectionMenuItem = menu.findItem(R.id.toggle_attack_detection);
        MenuItem toggleCellTrackingMenuItem = menu.findItem(R.id.toggle_cell_tracking);
        toggleAttackDetectionMenuItem.setChecked(mAimsicdService.isMonitoringCell());
        toggleCellTrackingMenuItem.setChecked(mAimsicdService.isTrackingCell());
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.toggle_attack_detection:
                monitorCell();
                item.setChecked(mAimsicdService.isMonitoringCell());
                break;
            case R.id.toggle_cell_tracking:
                trackCell();
                item.setChecked(mAimsicdService.isTrackingCell());
                break;
            case R.id.settings:
                Intent settingsIntent = new Intent(this, SettingsActivity.class);
                startActivity(settingsIntent);
                break;
            case R.id.about:
                Intent aboutIntent = new Intent(this, AboutActivity.class);
                startActivity(aboutIntent);
                break;
            case R.id.debugging:
                Intent i = new Intent(this, DebugLogs.class);
                startActivity(i);
                break;
        }

        return mDrawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
    }

    /**
     * Exit application if Back button is pressed twice
     */
    @Override
    public void onBackPressed() {
        Toast onBackPressedToast = Toast
                .makeText(this, R.string.press_once_again_to_exit, Toast.LENGTH_SHORT);
        long currentTime = System.currentTimeMillis();
        if (currentTime - mLastPress > 5000) {
            onBackPressedToast.show();
            mLastPress = currentTime;
        } else {
            onBackPressedToast.cancel();
            super.onBackPressed();
            try {
                if (mAimsicdService.isSmsTracking()) {
                    mAimsicdService.stopSmsTracking();
                }
            } catch (Exception ee) {
               Log.e(TAG, mTAG + "Error: Stopping SMS detection : " + ee.getMessage());
            }
            // Close database on Exit
            Log.i(TAG, mTAG + "Closing db from onBackPressed()");
            new AIMSICDDbAdapter(getApplicationContext()).close();
            finish();
        }
    }


    private void SmsDetection() {
        boolean root_sms = prefs.getBoolean(getString(R.string.adv_user_root_pref_key), false); // default is false

        if (root_sms && !mAimsicdService.isSmsTracking()) {
            mAimsicdService.startSmsTracking();
            Helpers.msgShort(this, "SMS Detection Started");
            Log.i(TAG, mTAG + "SMS Detection Thread Started");
        } else if (!root_sms && mAimsicdService.isSmsTracking()) {
            mAimsicdService.stopSmsTracking();
            Helpers.msgShort(this, "Sms Detection Stopped");
            Log.i(TAG, mTAG + "SMS Detection Thread Stopped");
        }
    }

    /**
     * Cell Information Tracking - Enable/Disable
     */
    private void trackCell() {
        mAimsicdService.setCellTracking(!mAimsicdService.isTrackingCell());
    }

    /**
     * Cell Information Monitoring - Enable/Disable
     */
    private void monitorCell() {
        mAimsicdService.setCellMonitoring(!mAimsicdService.isMonitoringCell());
    }

    public void onStop() {
        super.onStop();
        ((AppAIMSICD) getApplication()).detach(this);
    }

    @Override
    public void onStart() {
        super.onStart();
        ((AppAIMSICD) getApplication()).attach(this);
    }
} 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 enum Status {
    IDLE(R.string.status_idle, R.color.material_grey_400),   // Grey      0x7f0b008e
    OK(R.string.status_ok, R.color.material_light_green_A700),             // Green     0x7f0b00bf
    MEDIUM(R.string.status_medium, R.color.material_yellow_A700),          // Yellow    0x7f0b0122
    HIGH(R.string.status_high, R.color.material_orange_A700),              // Orange    0x7f0b00db
    DANGER(R.string.status_danger, R.color.material_red_A700),             // Red       0x7f0b0105
    SKULL(R.string.status_skull, R.color.material_black);                   // Black     0x7f0b002d

    @StringRes
    private int name;

    @ColorRes
    private int color;

    Status(@StringRes int name, @ColorRes int color) {
        this.name = name;
        this.color = color;
    }

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

    @ColorRes
    public int getColor() {
        return color;
    }
} 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 Device {

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


    public Cell mCell;
    private int mPhoneID = -1;
    private String mCellInfo;
    private String mDataState;
    private String mDataStateShort;
    private String mNetName;
    private String mMncmcc;
    private Optional<String> mSimCountry;
    private String mPhoneType;
    private String mIMEI;
    private String mIMEIV;
    private Optional<String> mSimOperator;
    private Optional<String> mSimOperatorName;
    private Optional<String> mSimSerial;
    private Optional<String> mSimSubs;
    private String mDataActivityType;
    private String mDataActivityTypeShort;
    private boolean mRoaming;

    private Location mLastLocation;

    /**
     * Refreshes all device specific details
     */
    public void refreshDeviceInfo(TelephonyManager tm, Context context) {

        //Phone type and associated details
        mIMEI = tm.getDeviceId();
        mIMEIV = tm.getDeviceSoftwareVersion();
        mPhoneID = tm.getPhoneType();
        mRoaming = tm.isNetworkRoaming();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            DeviceApi18.loadCellInfo(tm, this);
        }

        if (mCell == null) {
            mCell = new Cell();
        }

        switch (mPhoneID) {

            case TelephonyManager.PHONE_TYPE_NONE:
            case TelephonyManager.PHONE_TYPE_SIP:
            case TelephonyManager.PHONE_TYPE_GSM:
                mPhoneType = "GSM";
                mMncmcc = tm.getNetworkOperator();
                if (mMncmcc != null && mMncmcc.length() >= 5) {
                    try {
                        if (mCell.getMCC() == Integer.MAX_VALUE) {
                            mCell.setMCC(Integer.parseInt(tm.getNetworkOperator().substring(0, 3)));
                        }
                        if (mCell.getMNC() == Integer.MAX_VALUE) {
                            mCell.setMNC(Integer.parseInt(tm.getNetworkOperator().substring(3, 5)));
                        }
                    } catch (Exception e) {
                        Log.i(TAG, mTAG + "MncMcc parse exception: ", e);
                    }
                }
                mNetName = tm.getNetworkOperatorName();
                if (!mCell.isValid()) {
                    GsmCellLocation gsmCellLocation = (GsmCellLocation) tm.getCellLocation();
                    if (gsmCellLocation != null) {
                        mCell.setCID(gsmCellLocation.getCid());
                        mCell.setLAC(gsmCellLocation.getLac());
                        mCell.setPSC(gsmCellLocation.getPsc());
                    }
                }
                break;

            case TelephonyManager.PHONE_TYPE_CDMA:
                mPhoneType = "CDMA";
                if (!mCell.isValid()) {
                    CdmaCellLocation cdmaCellLocation = (CdmaCellLocation) tm.getCellLocation();
                    if (cdmaCellLocation != null) {
                        mCell.setCID(cdmaCellLocation.getBaseStationId());
                        mCell.setLAC(cdmaCellLocation.getNetworkId());
                        mCell.setSID(cdmaCellLocation.getSystemId()); // one of these must be a bug !!
                        // See: http://stackoverflow.com/questions/8088046/android-how-to-identify-carrier-on-cdma-network
                        // and: https://github.com/klinker41/android-smsmms/issues/26
                        mCell.setMNC(cdmaCellLocation.getSystemId()); // todo: check! (Also CellTracker.java)

                        //Retrieve MCC through System Property
                        String homeOperator = Helpers.getSystemProp(context,
                                "ro.cdma.home.operator.numeric", "UNKNOWN");
                        if (!homeOperator.contains("UNKNOWN")) {
                            try {
                                if (mCell.getMCC() == Integer.MAX_VALUE) {
                                    mCell.setMCC(Integer.valueOf(homeOperator.substring(0, 3)));
                                }
                                if (mCell.getMNC() == Integer.MAX_VALUE) {
                                    mCell.setMNC(Integer.valueOf(homeOperator.substring(3, 5)));
                                }
                            } catch (Exception e) {
                                Log.i(TAG, mTAG + "HomeOperator parse exception - " + e.getMessage(), e);
                            }
                        }
                    }
                }
                break;
        }

        // SIM Information
        mSimCountry = getSimCountry(tm);
        // Get the operator code of the active SIM (MCC + MNC)
        mSimOperator = getSimOperator(tm);
        mSimOperatorName = getSimOperatorName(tm);
        mSimSerial = getSimSerial(tm);
        mSimSubs = getSimSubs(tm);

        mDataActivityType = getDataActivity(tm);
        mDataState = getDataState(tm);
    }

    public String getCellInfo() {
        return mCellInfo;
    }

    public void setCellInfo(String cellInfo) {
        mCellInfo = cellInfo;
    }

    /**
     * Phone Type ID
     *
     * @return integer representation of Phone Type
     */
    public int getPhoneID() {
        return mPhoneID;
    }

    private Optional<String> getSimInformation(Supplier<String> simInfoSupplier) {
        try {
            return Optional.ofNullable(simInfoSupplier.get());
        } catch (Exception e) {
            // SIM methods can cause Exceptions on some devices
           Log.e(TAG, mTAG + "Failed to get SIM-Information", e);
        }
        return Optional.empty();
    }

    /**
     * SIM Country
     *
     * @return string of SIM Country data
     */
    Optional<String> getSimCountry(final TelephonyManager tm) {
        return getSimInformation(new Supplier<String>() {
            @Nullable
            @Override
            public String get() {
                return tm.getSimCountryIso();
            }
        });
    }

    /**
     * SIM Country data
     */
    public Optional<String> getSimCountry() {
        return mSimCountry;
    }

    /**
     * SIM Operator
     *
     * @return string of SIM Operator data
     */
    public Optional<String> getSimOperator(final TelephonyManager tm) {
        return getSimInformation(new Supplier<String>() {
            @Nullable
            @Override
            public String get() {
                return tm.getSimOperator();
            }
        });
    }

    public Optional<String> getSimOperator() {
        return mSimOperator;
    }

    /**
     * SIM Operator Name
     *
     * @return string of SIM Operator Name
     */
    Optional<String> getSimOperatorName(final TelephonyManager tm) {
        return getSimInformation(new Supplier<String>() {
            @Nullable
            @Override
            public String get() {
                return tm.getSimOperatorName();
            }
        });
    }

    public Optional<String> getSimOperatorName() {
        return mSimOperatorName;
    }

    /**
     * SIM Subscriber ID
     *
     * @return string of SIM Subscriber ID data
     */
    Optional<String> getSimSubs(final TelephonyManager tm) {
        return getSimInformation(new Supplier<String>() {
            @Nullable
            @Override
            public String get() {
                return tm.getSubscriberId();
            }
        });
    }

    public Optional<String> getSimSubs() {
        return mSimSubs;
    }

    /**
     * SIM Serial Number
     *
     * @return string of SIM Serial Number data
     */
    Optional<String> getSimSerial(final TelephonyManager tm) {
        return getSimInformation(new Supplier<String>() {
            @Nullable
            @Override
            public String get() {
                return tm.getSimSerialNumber();
            }
        });
    }

    public Optional<String> getSimSerial() {
        return mSimSerial;
    }

    public String getPhoneType() {
        return mPhoneType;
    }

    /**
     * IMEI
     *
     * @return string representing device IMEI
     */
    public String getIMEI() {
        return mIMEI;
    }

    /**
     * IMEI Version / Device Software Version
     *
     * @return string representing device IMEI Version
     */
    public String getIMEIv() {
        return mIMEIV;
    }

    /**
     * Sets Network Operator Name
     *
     */
    public void setNetworkName(String networkName) {
        mNetName = networkName;
    }

    public String getNetworkName() {
        return mNetName;
    }

    /**
     * Network Operator
     *
     * @return string representing the Network Operator
     */
    public String getMncMcc() {
        return mMncmcc;
    }

    /**
     * Network Type
     *
     * @return string representing device Network Type
     */
    public String getNetworkTypeName() {
        if (mCell == null) {
            return "Unknown";
        }

        return mCell.getRAT();
    }

    String getDataActivity(TelephonyManager tm) {
        int direction = tm.getDataActivity();
        mDataActivityTypeShort = "un";
        mDataActivityType = "undef";

        switch (direction) {
            case TelephonyManager.DATA_ACTIVITY_NONE:
                mDataActivityTypeShort = "No";
                mDataActivityType = "None";
                break;
            case TelephonyManager.DATA_ACTIVITY_IN:
                mDataActivityTypeShort = "In";
                mDataActivityType = "In";
                break;
            case TelephonyManager.DATA_ACTIVITY_OUT:
                mDataActivityTypeShort = "Ou";
                mDataActivityType = "Out";
                break;
            case TelephonyManager.DATA_ACTIVITY_INOUT:
                mDataActivityTypeShort = "IO";
                mDataActivityType = "In-Out";
                break;
            case TelephonyManager.DATA_ACTIVITY_DORMANT:
                mDataActivityTypeShort = "Do";
                mDataActivityType = "Dormant";
                break;
        }

        return mDataActivityType;
    }

    public String getDataActivity() {
        return mDataActivityType;
    }

    String getDataState(TelephonyManager tm) {
        int state = tm.getDataState();
        mDataState = "undef";
        mDataStateShort = "un";
        switch (state) {
            case TelephonyManager.DATA_DISCONNECTED:
                mDataState = "Disconnected";
                mDataStateShort = "Di";
                break;
            case TelephonyManager.DATA_CONNECTING:
                mDataState = "Connecting";
                mDataStateShort = "Ct";
                break;
            case TelephonyManager.DATA_CONNECTED:
                mDataState = "Connected";
                mDataStateShort = "Cd";
                break;
            case TelephonyManager.DATA_SUSPENDED:
                mDataState = "Suspended";
                mDataStateShort = "Su";
                break;
        }

        return mDataState;
    }

    public String getDataState() {
        return mDataState;
    }

    public String getDataActivityTypeShort() {
        return mDataActivityTypeShort;
    }

    public void setDataActivityTypeShort(String dataActivityTypeShort) {
        mDataActivityTypeShort = dataActivityTypeShort;
    }

    public String getDataStateShort() {
        return mDataStateShort;
    }

    public void setDataStateShort(String dataStateShort) {
        mDataStateShort = dataStateShort;
    }

    public void setDataActivityType(String dataActivityType) {
        mDataActivityType = dataActivityType;
    }

    public void setDataState(String dataState) {
        mDataState = dataState;
    }

    public void setSignalDbm(int signalDbm) {
        mCell.setDBM(signalDbm);
    }

    public int getSignalDBm() {
        return mCell.getDBM();
    }

    /**
     * Update Network Type
     */
    public void setNetID(TelephonyManager tm) {
        mCell.setNetType(tm.getNetworkType());
    }

    /**
     * Mobile Roaming
     *
     * @return string representing Roaming status (True/False)
     */
    public String isRoaming() {
        return String.valueOf(mRoaming);
    }

    public void setLastLocation(Location location) {
        mLastLocation = location;
    }

    /**
     * Attempts to retrieve the Last Known Location from the device
     *
     * @return Cell object representing last known location
     */
    public Location getLastLocation() {
        return mLastLocation;
    }
} @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class DeviceApi18 {

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


    public static void loadCellInfo(TelephonyManager tm, Device pDevice) {
        int lCurrentApiVersion = android.os.Build.VERSION.SDK_INT;
        try {
            if (pDevice.mCell == null) {
                pDevice.mCell = new Cell();
            }
            List<CellInfo> cellInfoList = tm.getAllCellInfo();
            if (cellInfoList != null) {
                for (final CellInfo info : cellInfoList) {

                    //Network Type
                    pDevice.mCell.setNetType(tm.getNetworkType());

                    if (info instanceof CellInfoGsm) {
                        final CellSignalStrengthGsm gsm = ((CellInfoGsm) info).getCellSignalStrength();
                        final CellIdentityGsm identityGsm = ((CellInfoGsm) info).getCellIdentity();
                        // Signal Strength
                        pDevice.mCell.setDBM(gsm.getDbm()); // [dBm]
                        // Cell Identity
                        pDevice.mCell.setCID(identityGsm.getCid());
                        pDevice.mCell.setMCC(identityGsm.getMcc());
                        pDevice.mCell.setMNC(identityGsm.getMnc());
                        pDevice.mCell.setLAC(identityGsm.getLac());

                    } else if (info instanceof CellInfoCdma) {
                        final CellSignalStrengthCdma cdma = ((CellInfoCdma) info).getCellSignalStrength();
                        final CellIdentityCdma identityCdma = ((CellInfoCdma) info).getCellIdentity();
                        // Signal Strength
                        pDevice.mCell.setDBM(cdma.getDbm());
                        // Cell Identity
                        pDevice.mCell.setCID(identityCdma.getBasestationId());
                        pDevice.mCell.setMNC(identityCdma.getSystemId());
                        pDevice.mCell.setLAC(identityCdma.getNetworkId());
                        pDevice.mCell.setSID(identityCdma.getSystemId());

                    } else if (info instanceof CellInfoLte) {
                        final CellSignalStrengthLte lte = ((CellInfoLte) info).getCellSignalStrength();
                        final CellIdentityLte identityLte = ((CellInfoLte) info).getCellIdentity();
                        // Signal Strength
                        pDevice.mCell.setDBM(lte.getDbm());
                        pDevice.mCell.setTimingAdvance(lte.getTimingAdvance());
                        // Cell Identity
                        pDevice.mCell.setMCC(identityLte.getMcc());
                        pDevice.mCell.setMNC(identityLte.getMnc());
                        pDevice.mCell.setCID(identityLte.getCi());

                    } else if  (lCurrentApiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2 && info instanceof CellInfoWcdma) {
                        final CellSignalStrengthWcdma wcdma = ((CellInfoWcdma) info).getCellSignalStrength();
                        final CellIdentityWcdma identityWcdma = ((CellInfoWcdma) info).getCellIdentity();
                        // Signal Strength
                        pDevice.mCell.setDBM(wcdma.getDbm());
                        // Cell Identity
                        pDevice.mCell.setLAC(identityWcdma.getLac());
                        pDevice.mCell.setMCC(identityWcdma.getMcc());
                        pDevice.mCell.setMNC(identityWcdma.getMnc());
                        pDevice.mCell.setCID(identityWcdma.getCid());
                        pDevice.mCell.setPSC(identityWcdma.getPsc());

                    } else {
                        Log.i(TAG, mTAG + "Unknown type of cell signal!"
                                + "\n ClassName: " + info.getClass().getSimpleName()
                                + "\n ToString: " + info.toString());
                    }
                    if (pDevice.mCell.isValid()) {
                        break;
                    }
                }
            }
        } catch (NullPointerException npe) {
           Log.e(TAG, mTAG + "loadCellInfo: Unable to obtain cell signal information: ", npe);
        }
    }

    public static void startListening(TelephonyManager tm, PhoneStateListener listener) {
        tm.listen(listener,
                PhoneStateListener.LISTEN_CELL_INFO |
                PhoneStateListener.LISTEN_CELL_LOCATION |
                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
                PhoneStateListener.LISTEN_SERVICE_STATE |
                PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
        );
    }
}  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 TinyDB {

    private SharedPreferences preferences;

    //prevent direct initialisation
    private TinyDB() {
    }

    public void init(Context appContext) {
        preferences = PreferenceManager.getDefaultSharedPreferences(appContext);
    }

    public String getString(String key) {
        return preferences.getString(key, "");
    }

    public void putInt(String key, int value) {
        SharedPreferences.Editor editor = preferences.edit();
        editor.putInt(key, value);
        editor.apply();
    }

    public void putBoolean(String key, boolean value) {
        SharedPreferences.Editor editor = preferences.edit();
        editor.putBoolean(key, value);
        editor.apply();
    }

    public boolean getBoolean(String key) {
        return preferences.getBoolean(key, false);
    }

    public void remove(String key) {
        SharedPreferences.Editor editor = preferences.edit();
        editor.remove(key);
        editor.apply();
    }

    public void clear() {
        SharedPreferences.Editor editor = preferences.edit();
        editor.clear();
        editor.apply();
    }

    public Map<String, ?> getAll() {
        return preferences.getAll();
    }

    private static class InstanceHolder {
        private static final TinyDB INSTANCE = new TinyDB();
    }

    public static TinyDB getInstance() {
        return InstanceHolder.INSTANCE;
    }
}
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Vibrator;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import zz.aimsicd.lite.AppAIMSICD;
import zz.aimsicd.lite.BuildConfig;
import zz.aimsicd.lite.MainActivity;
import zz.aimsicd.lite.R;
import zz.aimsicd.lite.adapters.AIMSICDDbAdapter;
import zz.aimsicd.lite.enums.Status;
import zz.aimsicd.lite.utils.Cell;
import zz.aimsicd.lite.utils.Device;
import zz.aimsicd.lite.utils.DeviceApi18;
import zz.aimsicd.lite.utils.Helpers;
import zz.aimsicd.lite.utils.Icon;
import zz.aimsicd.lite.utils.TinyDB;
            // 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);