edu.cens.loci.provider.LociDbUtils.java Source code

Java tutorial

Introduction

Here is the source code for edu.cens.loci.provider.LociDbUtils.java

Source

/*******************************************************************************
 * Copyright 2012 The Regents of the University of California
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package edu.cens.loci.provider;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.location.Location;
import android.location.LocationManager;
import android.text.TextUtils;
import edu.cens.loci.LociConfig;
import edu.cens.loci.classes.LociCircleArea;
import edu.cens.loci.classes.LociLocation;
import edu.cens.loci.classes.LociPlace;
import edu.cens.loci.classes.LociVisit;
import edu.cens.loci.classes.LociVisitWifi;
import edu.cens.loci.classes.LociWifiFingerprint;
import edu.cens.loci.classes.LociVisit.RecognitionResult;
import edu.cens.loci.provider.LociContract.Data;
import edu.cens.loci.provider.LociContract.MimetypesColumns;
import edu.cens.loci.provider.LociContract.Places;
import edu.cens.loci.provider.LociContract.Tracks;
import edu.cens.loci.provider.LociContract.Visits;
import edu.cens.loci.provider.LociContract.CommonPlaceDataKinds.GpsCircleArea;
import edu.cens.loci.provider.LociContract.CommonPlaceDataKinds.Keyword;
import edu.cens.loci.provider.LociContract.CommonPlaceDataKinds.WifiFingerprint;
import edu.cens.loci.provider.LociContract.CommonPlaceDataKinds.WifiFingerprint.WifiDataQuery;
import edu.cens.loci.provider.LociDatabaseHelper.DataColumns;
import edu.cens.loci.provider.LociDatabaseHelper.Tables;
import edu.cens.loci.utils.MyDateUtils;
import edu.cens.loci.utils.MyLog;

public class LociDbUtils {

    private static final String TAG = "LociDbUtils";

    private LociDatabaseHelper mDbHelper = null;

    public LociDbUtils(Context context) {
        mDbHelper = getDatabaseHelper(context);
        initDataRowHandler();
    }

    /* Visible for testing */
    protected LociDatabaseHelper getDatabaseHelper(final Context context) {
        return LociDatabaseHelper.getInstance(context);
    }

    /** Sql for updating DIRTY flag on multiple places */
    private static final String UPDATE_PLACE_SET_DIRTY_SQL = "UPDATE " + Tables.PLACES + " SET " + Places.DIRTY
            + "=1" + " WHERE " + Places._ID + " IN (";

    /** Sql for updating VERSION on multiple places */
    private static final String UPDATE_PLACE_SET_VERSION_SQL = "UPDATE " + Tables.PLACES + " SET " + Places.VERSION
            + " = " + Places.VERSION + " + 1" + " WHERE " + Places._ID + " IN (";

    /* package */ static final String UPDATE_TIMES_VISITED_PLACES_TABLE = "UPDATE " + Tables.PLACES + " SET "
            + Places.TIMES_VISITED + "=" + " CASE WHEN " + Places.TIMES_VISITED + " IS NULL THEN 1 ELSE " + " ("
            + Places.TIMES_VISITED + " + 1) END WHERE " + Places._ID + "=?";

    private HashSet<Long> mUpdatedPlaces = new HashSet<Long>();
    private HashSet<Long> mDirtyPlaces = new HashSet<Long>();

    public long insertSuggestedWifiPlace(LociVisitWifi visit) {
        // create a name 
        int num = countPlacesByEntry(Places.ENTRY_WIFI_SUGGESTED);
        String name = String.format("New Place #%03d", num + 1);
        long time = System.currentTimeMillis();
        long placeId = insertPlace(name, Places.STATE_SUGGESTED, Places.TYPE_WIFI, Places.ENTRY_WIFI_SUGGESTED,
                time, time);
        insertWifiFingerprint(placeId, visit.wifi);
        return placeId;
    }

    public long insertPlace(String name, int state, int type, int entry, long entryTime, long registerTime) {

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();

        ContentValues args = new ContentValues();
        args.put(Places.PLACE_NAME, name);
        args.put(Places.PLACE_STATE, state);
        args.put(Places.PLACE_TYPE, type);
        args.put(Places.ENTRY, entry);
        args.put(Places.ENTRY_TIME, entryTime);
        args.put(Places.REGISTER_TIME, registerTime);

        return db.insert(Tables.PLACES, null, args);
    }

    public int deletePlace(long placeId) {
        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        return db.delete(Tables.PLACES, Places._ID + "=" + String.valueOf(placeId), null);
    }

    public int delete(SQLiteDatabase db, Cursor c) {
        long dataId = c.getLong(DataDeleteQuery._ID);
        //long placeId = c.getLong(DataDeleteQuery.PLACE_ID);
        mSelectionArgs1[0] = String.valueOf(dataId);
        int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1);
        return count;
    }

    public void checkPlaceTable() {

        if (!LociConfig.D.DB.DEBUG)
            return;

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();

        Cursor cursor = db.query(Tables.PLACES, null, null, null, null, null, null);

        MyLog.d(LociConfig.D.DB.DEBUG, TAG, "[DB] checkPlaceTable: #places=" + cursor.getCount());

        if (cursor.moveToFirst()) {
            do {
                long id = cursor.getLong(cursor.getColumnIndex(Places._ID));
                String name = cursor.getString(cursor.getColumnIndex(Places.PLACE_NAME));
                int type = cursor.getInt(cursor.getColumnIndex(Places.PLACE_TYPE));
                int state = cursor.getInt(cursor.getColumnIndex(Places.PLACE_STATE));
                MyLog.d(LociConfig.D.DB.DEBUG, TAG,
                        String.format("   id=%d name=%s type=%d state=%d", id, name, type, state));
            } while (cursor.moveToNext());
        }
        cursor.close();
    }

    public LociPlace getPlace(long placeId) {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();

        Cursor cursor = db.query(Tables.PLACES, null, Places._ID + "=" + placeId, null, null, null, null);

        if (cursor.moveToFirst()) {
            LociPlace place = new LociPlace();
            place.placeId = cursor.getLong(cursor.getColumnIndex(Places._ID));
            place.name = cursor.getString(cursor.getColumnIndex(Places.PLACE_NAME));
            place.state = cursor.getInt(cursor.getColumnIndex(Places.PLACE_STATE));
            place.type = cursor.getInt(cursor.getColumnIndex(Places.PLACE_TYPE));
            place.entry = cursor.getInt(cursor.getColumnIndex(Places.ENTRY));
            place.entryTime = cursor.getLong(cursor.getColumnIndex(Places.ENTRY_TIME));
            place.registerTime = cursor.getLong(cursor.getColumnIndex(Places.REGISTER_TIME));

            place.wifis = getWifiFingerprint(placeId);
            place.areas = getGpsCircleArea(placeId);

            if (place.areas == null || place.areas.size() == 0)
                place.areas.add(getPlacePositionEstimate(place.placeId));

            cursor.close();
            return place;
        }

        cursor.close();
        return null;
    }

    public LociCircleArea getPlacePositionEstimate(long placeId) {
        ArrayList<LociVisit> visits = getBaseVisits(placeId, Visits.ENTER + " DESC", null);

        LociLocation placeLoc = null;

        MyLog.i(LociConfig.D.DB.UTILS, TAG,
                String.format("[DB] estimating position from %d visits.", visits.size()));

        // first, check if we have a good location fix near stay time from all visits
        for (LociVisit visit : visits) {
            long enter = visit.enter;
            long exit = visit.exit;

            placeLoc = getAveragePosition(enter - 30000, exit + 30000, 100);

            if (placeLoc != null && placeLoc.isValid())
                break;
        }

        if (placeLoc == null || !placeLoc.isValid()) {
            float minAccuracy = Float.MAX_VALUE;
            LociLocation tmpLoc = null;

            MyLog.i(LociConfig.D.DB.UTILS, TAG,
                    String.format("[DB] estimated positions while staying had low accuracy, try bad accuracy."));

            for (LociVisit visit : visits) {
                tmpLoc = getPlaceLocationEstimationWithBestAccuracy(visit.enter - 30000, visit.exit + 30000);

                if (tmpLoc != null)
                    MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" => lat=%f lon=%f acc=%f",
                            tmpLoc.getLatitude(), tmpLoc.getLongitude(), tmpLoc.getAccuracy()));
                else
                    MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" => no location for this visit.."));

                if (tmpLoc != null && minAccuracy >= tmpLoc.getAccuracy()) {
                    placeLoc = tmpLoc;
                    minAccuracy = placeLoc.getAccuracy();
                }
            }
            if (placeLoc != null)
                MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" ++ picked lat=%f lon=%f acc=%f",
                        placeLoc.getLatitude(), placeLoc.getLongitude(), placeLoc.getAccuracy()));
            else
                MyLog.i(LociConfig.D.DB.UTILS, TAG, " ++ no location satisfying condition.");

        } else {
            MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] (1) got an estimate within a stay with good accuracy.");
            MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" ++ picked lat=%f lon=%f acc=%f",
                    placeLoc.getLatitude(), placeLoc.getLongitude(), placeLoc.getAccuracy()));
            // add to state
            LociCircleArea circle = new LociCircleArea();
            circle.setCenter(placeLoc);
            circle.setRadius(placeLoc.getAccuracy());
            circle.extra = "while staying";
            return circle;
        }

        // if not, get the closest location fix from stay time
        if (placeLoc == null || !placeLoc.isValid()) {
            MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] don't have an estimate within a stay, try closest.");
            long minOffTime = Long.MAX_VALUE;
            String finalExtra = "";
            String extra = "";
            long offTime = Long.MAX_VALUE;
            LociLocation tmpLoc = null;

            for (LociVisit visit : visits) {
                LociLocation before = getFirstLocationBeforeOrAfterTime(visit.enter, true);
                LociLocation after = getFirstLocationBeforeOrAfterTime(visit.exit, false);

                if (before != null && after != null) {

                    MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] has both before and after.");

                    if (Math.abs(before.getTime() - visit.enter) < Math.abs(after.getTime() - visit.exit)) {
                        tmpLoc = before;
                        offTime = visit.enter - before.getTime();
                        extra = MyDateUtils.humanReadableDuration(offTime, 2) + "before entering";
                    } else {
                        tmpLoc = after;
                        offTime = after.getTime() - visit.exit;
                        extra = MyDateUtils.humanReadableDuration(offTime, 2) + "after exiting";
                    }
                } else if (before != null) {
                    MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] has before.");

                    tmpLoc = before;
                    offTime = visit.enter - before.getTime();
                    extra = MyDateUtils.humanReadableDuration(offTime, 2) + "before entering";
                } else if (after != null) {
                    MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] has after.");
                    tmpLoc = after;
                    offTime = after.getTime() - visit.exit;
                    extra = MyDateUtils.humanReadableDuration(offTime, 2) + "after exiting";
                } else {
                    MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] has none.");
                    continue;
                }

                MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format("[DB] offtime=%d extra=%s", offTime, extra));

                if (offTime < minOffTime) {
                    minOffTime = offTime;
                    finalExtra = extra;
                    placeLoc = tmpLoc;
                }
            }

            if (placeLoc != null && placeLoc.isValid()) {
                LociCircleArea circle = new LociCircleArea();
                circle.setCenter(placeLoc);
                circle.setRadius(placeLoc.getAccuracy());
                circle.extra = finalExtra;
                MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] (3) got an estimate near stay");
                MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" ++ picked lat=%f lon=%f acc=%f",
                        placeLoc.getLatitude(), placeLoc.getLongitude(), placeLoc.getAccuracy()));
                return circle;
            } else {
                MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] has no gps.");
            }

        } else {
            MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] (2) got an estimate within a stay with ok accuracy");
            // add to state

            LociCircleArea circle = new LociCircleArea();
            circle.setCenter(placeLoc);
            circle.setRadius(placeLoc.getAccuracy());
            circle.extra = "while staying (low accuracy)";

            MyLog.i(LociConfig.D.DB.UTILS, TAG, String.format(" ++ picked lat=%f lon=%f acc=%f",
                    placeLoc.getLatitude(), placeLoc.getLongitude(), placeLoc.getAccuracy()));

            return circle;
        }

        //TODO: What should we do when no position?
        MyLog.i(LociConfig.D.DB.UTILS, TAG, "[DB] (4) no estimate");
        LociCircleArea circle = new LociCircleArea();
        circle.setCenter(34.06945, -118.443);
        circle.setRadius(100);
        circle.extra = "location is not available, default location.";
        return null;
    }

    public int updatePlaceBasic(LociPlace place) {
        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Places.PLACE_NAME, place.name);
        args.put(Places.PLACE_STATE, place.state);
        args.put(Places.PLACE_TYPE, place.type);
        return db.update(Tables.PLACES, args, Places._ID + "=" + place.placeId, null);
    }

    public int updatePlaceState(long placeId, int state) {
        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Places.PLACE_STATE, state);
        return db.update(Tables.PLACES, args, Places._ID + "=" + placeId, null);
    }

    public ArrayList<LociPlace> getPlaces(String selection) {

        ArrayList<LociPlace> places = new ArrayList<LociPlace>();

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();

        String orderBy = Places.PLACE_NAME + " ASC";

        Cursor cursor = db.query(Tables.PLACES, null, selection, null, null, null, orderBy);

        if (cursor.moveToFirst()) {
            do {
                LociPlace place = new LociPlace();
                place.placeId = cursor.getLong(cursor.getColumnIndex(Places._ID));
                place.name = cursor.getString(cursor.getColumnIndex(Places.PLACE_NAME));
                place.state = cursor.getInt(cursor.getColumnIndex(Places.PLACE_STATE));
                place.type = cursor.getInt(cursor.getColumnIndex(Places.PLACE_TYPE));
                place.entry = cursor.getInt(cursor.getColumnIndex(Places.ENTRY));
                place.entryTime = cursor.getLong(cursor.getColumnIndex(Places.ENTRY_TIME));
                place.registerTime = cursor.getLong(cursor.getColumnIndex(Places.REGISTER_TIME));

                place.wifis = getWifiFingerprint(place.placeId);
                place.areas = getGpsCircleArea(place.placeId);

                if (place.state == Places.STATE_DELETED)
                    continue;

                places.add(place);
            } while (cursor.moveToNext());
        }
        cursor.close();
        return places;
    }

    public ArrayList<LociPlace> sortPlacesByWifiSimilarity(ArrayList<LociPlace> srcPlaces,
            LociWifiFingerprint targetWifi) {

        ArrayList<LociPlace> dstPlaces = new ArrayList<LociPlace>();
        ArrayList<RecognitionResult> results = new ArrayList<RecognitionResult>();

        int listsize = 0;

        for (LociPlace place : srcPlaces) {

            if (place.wifis == null) {
                MyLog.d(LociConfig.D.DB.DEBUG, TAG,
                        String.format("[DB] sortPlacesByWifiSimilarity: no wifis, skip. (pid=%d)", place.placeId));
                results.add(new RecognitionResult(-1, place.placeId, -1, 0));
                place.extra_double = 0;
                dstPlaces.add(place);
                continue;
            }

            double bestScorePerPlace = 0;

            for (LociWifiFingerprint wifi : place.wifis) {
                double score = recognitionAlgorithm(targetWifi, wifi);
                if (score > bestScorePerPlace) {
                    bestScorePerPlace = score;
                }
            }
            place.extra_double = bestScorePerPlace;
            listsize = results.size();
            if (listsize == 0) {
                results.add(new RecognitionResult(-1, place.placeId, -1, bestScorePerPlace));
                dstPlaces.add(place);
            } else {
                for (int i = 0; i < listsize; i++) {
                    if (bestScorePerPlace > results.get(i).score) {
                        results.add(i, new RecognitionResult(-1, place.placeId, -1, bestScorePerPlace));
                        dstPlaces.add(i, place);
                        break;
                    }
                }

                if (listsize == results.size()) {
                    results.add(new RecognitionResult(-1, place.placeId, -1, bestScorePerPlace));
                    dstPlaces.add(place);
                }
                listsize = results.size();
            }
        }

        return dstPlaces;
    }

    public int countPlacesByEntry(int entry) {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = db.query(Tables.PLACES, new String[] { Places._ID, Places.ENTRY },
                Places.ENTRY + "=" + entry, null, null, null, null);
        if (cursor != null)
            return cursor.getCount();

        return 0;
    }

    /***
     * Tables.Data related methods
     */

    private ContentValues mValues = new ContentValues();

    private HashMap<String, DataRowHandler> mDataRowHandlers;

    private void initDataRowHandler() {
        mDataRowHandlers = new HashMap<String, DataRowHandler>();
        mDataRowHandlers.put(WifiFingerprint.CONTENT_ITEM_TYPE, new WifiFingerprintRowHandler());
        mDataRowHandlers.put(GpsCircleArea.CONTENT_ITEM_TYPE, new GpsAreaRowHandler());
        mDataRowHandlers.put(Keyword.CONTENT_ITEM_TYPE, new KeywordRowHandler());
    }

    private DataRowHandler getDataRowHandler(final String mimeType) {
        DataRowHandler handler = mDataRowHandlers.get(mimeType);
        if (handler == null) {
            handler = new CustomDataRowHandler(mimeType);
            mDataRowHandlers.put(mimeType, handler);
        }
        return handler;
    }

    /**
     * 
     * @param values
     * @param callerIsSyncAdapter
     * @return Data._id of the new row
     */
    private long insertData(ContentValues values, boolean callerIsSyncAdapter) {
        long id = 0;
        mValues.clear();
        mValues.putAll(values);

        long placeId = mValues.getAsLong(Data.PLACE_ID);

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();

        // Replace mimetype with internal mapping
        final String mimeType = mValues.getAsString(Data.MIMETYPE);
        if (TextUtils.isEmpty(mimeType)) {
            throw new IllegalArgumentException(Data.MIMETYPE + " is required");
        }

        mValues.put(DataColumns.MIMETYPE_ID, mDbHelper.getMimeTypeId(mimeType));
        mValues.remove(Data.MIMETYPE);

        DataRowHandler rowHandler = getDataRowHandler(mimeType);
        id = rowHandler.insert(db, placeId, mValues);
        if (!callerIsSyncAdapter)
            setPlaceDirty(placeId);
        mUpdatedPlaces.add(placeId);

        return id;
    }

    public int updateData(ContentValues values, String selection, String[] selectionArgs,
            boolean callerIsSyncAdapter) {
        mValues.clear();
        mValues.putAll(values);
        mValues.remove(Data._ID);
        mValues.remove(Data.PLACE_ID);
        mValues.remove(Data.MIMETYPE);

        int count = 0;
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor c = db.query(Tables.DATA, DataUpdateQuery.COLUMNS, selection, selectionArgs, null, null, null);
        try {
            while (c.moveToNext()) {
                count += updateData(mValues, c, callerIsSyncAdapter);
            }
        } finally {
            c.close();
        }

        return count;
    }

    private int updateData(ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
        if (values.size() == 0)
            return 0;

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();

        final String mimeType = c.getString(DataUpdateQuery.MIMETYPE);
        DataRowHandler rowHandler = getDataRowHandler(mimeType);
        if (rowHandler.update(db, values, c, callerIsSyncAdapter)) {
            return 1;
        } else {
            return 0;
        }
    }

    /**
     * Delete data row by row so that fixing of primaries etc work correctly.
     */
    public int deleteData(String selection, String[] selectionArgs, boolean callerIsSyncAdapter) {
        int count = 0;
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        // Note that the query will return data according to the access restrictions,
        // so we don't need to worry about deleting data we don't have permission to read.
        Cursor c = db.query(Tables.DATA, DataDeleteQuery.COLUMNS, selection, selectionArgs, null, null, null);
        try {
            while (c.moveToNext()) {
                long placeId = c.getLong(DataDeleteQuery.PLACE_ID);
                String mimeType = c.getString(DataDeleteQuery.MIMETYPE);
                DataRowHandler rowHandler = getDataRowHandler(mimeType);
                count += rowHandler.delete(db, c);
                if (!callerIsSyncAdapter) {
                    setPlaceDirty(placeId);
                }
            }
        } finally {
            c.close();
        }

        return count;
    }

    /**
     * 
     * @param visit
     * @return Data._id of the new row
     */
    public long insertWifiFingerprint(long placeId, LociWifiFingerprint wifi) {
        boolean callerIsSyncAdapter = false;
        ContentValues values = new ContentValues();
        values.put(Data.MIMETYPE, WifiFingerprint.CONTENT_ITEM_TYPE);
        values.put(Data.PLACE_ID, placeId);
        try {
            values.put(WifiFingerprint.FINGERPRINT, wifi.toJsonObject().toString());
        } catch (JSONException e) {
            MyLog.e(LociConfig.D.JSON, TAG, "insertWifiFingerprint : json error.");
            e.printStackTrace();
            return -1;
        }
        values.put(WifiFingerprint.TIMESTAMP, wifi.getEnter());
        return insertData(values, callerIsSyncAdapter);
    }

    public ArrayList<LociWifiFingerprint> getWifiFingerprint(long placeId) {
        ArrayList<LociWifiFingerprint> wifi = new ArrayList<LociWifiFingerprint>();
        Cursor cursor = getPlaceData(placeId, WifiFingerprint.CONTENT_ITEM_TYPE);

        if (cursor.moveToFirst()) {
            do {
                String wifiJson = cursor.getString(cursor.getColumnIndex(WifiFingerprint.FINGERPRINT));

                try {
                    wifi.add(new LociWifiFingerprint(wifiJson));
                } catch (JSONException e) {
                    MyLog.e(LociConfig.D.JSON, TAG, "getWifiFingerprint(): json error.");
                    e.printStackTrace();
                    continue;
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
        return wifi;
    }

    public Cursor getPlaceData(long placeId, String mimeType) {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        //checkDataTable();
        String selection = (Data.PLACE_ID + "=" + placeId) + " AND " + DataColumns.MIMETYPE_ID + "="
                + mDbHelper.getMimeTypeId(mimeType);
        Cursor cursor = db.query(Tables.DATA, null, selection, null, null, null, null);
        return cursor;
    }

    public Cursor getPlaceData(String mimeType) {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        //checkDataTable();
        String selection = DataColumns.MIMETYPE_ID + "=" + mDbHelper.getMimeTypeId(mimeType);
        Cursor cursor = db.query(Tables.DATA, null, selection, null, null, null, null);
        return cursor;
    }

    public void checkDataTable() {

        if (!LociConfig.D.DB.DEBUG)
            return;

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = db.query(Tables.DATA, null, null, null, null, null, null);

        MyLog.d(LociConfig.D.DB.DEBUG, TAG, "[DB] checkDataTable: #rows=" + cursor.getCount());

        if (cursor.moveToFirst()) {
            do {
                long id = cursor.getLong(cursor.getColumnIndex(Data._ID));
                long pid = cursor.getLong(cursor.getColumnIndex(Data.PLACE_ID));
                int mimeTypeId = cursor.getInt(cursor.getColumnIndex(DataColumns.MIMETYPE_ID));

                MyLog.d(LociConfig.D.DB.DEBUG, TAG,
                        String.format("  id=%d pid=%d mimetype=%d", id, pid, mimeTypeId));

            } while (cursor.moveToNext());
        }

        cursor.close();
    }

    /**
     * 
     * @param wifi
     * @return Recognition results with score higher than 0, sorted by score
     *              Recognition result contains Recognition time, PlaceId, FingerprintId, Score
     */
    public ArrayList<RecognitionResult> getRecogntionScoresWifi(LociWifiFingerprint wifi) {
        ArrayList<RecognitionResult> results = new ArrayList<RecognitionResult>();

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();

        checkPlaceTable();

        //checkDataTable();

        long mimeTypeId = mDbHelper.getMimeTypeId(WifiFingerprint.CONTENT_ITEM_TYPE);
        String selection = DataColumns.MIMETYPE_ID + "=" + mimeTypeId;
        Cursor cursor = db.query(Tables.DATA, WifiDataQuery.COLUMNS, selection, null, null, null,
                Data.PLACE_ID + " ASC");

        long recogTime = System.currentTimeMillis();
        if (cursor.moveToFirst()) {

            do {
                long placeId = cursor.getLong(WifiDataQuery.PLACEID);
                LociWifiFingerprint dbWifi;
                try {
                    dbWifi = new LociWifiFingerprint(cursor.getString(WifiDataQuery.FINGERPRINT));
                } catch (JSONException e) {
                    MyLog.e(LociConfig.D.JSON, TAG, "getRecognitionScoresWifi : json error.");
                    e.printStackTrace();
                    continue;
                }
                long timestamp = cursor.getLong(WifiDataQuery.TIMESTAMP);
                double score = recognitionAlgorithm(dbWifi, wifi);

                if (score > 0) {
                    RecognitionResult result = new RecognitionResult(recogTime, placeId, timestamp, score);

                    // sort the results by score, index 0 contains the highest score
                    int resultsSize = results.size();
                    for (int i = 0; i < resultsSize; i++) {
                        if (score > results.get(i).score) {
                            results.add(i, result);
                            break;
                        }
                    }
                    if (results.size() == resultsSize)
                        results.add(result);
                }
            } while (cursor.moveToNext());
        }
        cursor.close();

        MyLog.d(LociConfig.D.PD.SCORE, TAG, "[getRecognitionScoresWifi] #results=" + results.size());
        for (RecognitionResult result : results) {
            MyLog.d(LociConfig.D.PD.SCORE, TAG, result.toString());
        }

        return results;
    }

    private double recognitionAlgorithm(LociWifiFingerprint dbWifi, LociWifiFingerprint wifi) {
        return LociWifiFingerprint.tanimotoScore(dbWifi, wifi, dbWifi.getRepAPs(), dbWifi.getRepAPs(), 0.3, true);
    }

    public long insertGpsCircleArea(long placeId, LociCircleArea area) {
        boolean callerIsSyncAdapter = false;
        ContentValues values = new ContentValues();
        values.put(Data.MIMETYPE, GpsCircleArea.CONTENT_ITEM_TYPE);
        values.put(Data.PLACE_ID, placeId);
        values.put(GpsCircleArea.LATITUDE, area.getLatitude());
        values.put(GpsCircleArea.LONGITUDE, area.getLongitude());
        values.put(GpsCircleArea.RADIUS, area.getRadius());
        return insertData(values, callerIsSyncAdapter);
    }

    public ArrayList<LociCircleArea> getGpsCircleArea(long placeId) {
        ArrayList<LociCircleArea> areas = new ArrayList<LociCircleArea>();
        Cursor cursor = getPlaceData(placeId, GpsCircleArea.CONTENT_ITEM_TYPE);

        if (cursor.moveToFirst()) {
            do {
                double latitude = cursor.getDouble(cursor.getColumnIndex(GpsCircleArea.LATITUDE));
                double longitude = cursor.getDouble(cursor.getColumnIndex(GpsCircleArea.LONGITUDE));
                float radius = cursor.getFloat(cursor.getColumnIndex(GpsCircleArea.RADIUS));

                areas.add(new LociCircleArea(latitude, longitude, radius));

            } while (cursor.moveToNext());
        }
        cursor.close();

        if (areas.size() <= 0) {

        }

        return areas;
    }

    public long insertKeyword(long placeId, String keyword) {
        boolean callerIsSyncAdapter = false;
        ContentValues values = new ContentValues();
        values.put(Data.MIMETYPE, GpsCircleArea.CONTENT_ITEM_TYPE);
        values.put(Data.PLACE_ID, placeId);
        values.put(Keyword.TAG, keyword);
        return insertData(values, callerIsSyncAdapter);
    }

    public ArrayList<String> getSavedKeywords() {
        Cursor cursor = getPlaceData(Keyword.CONTENT_ITEM_TYPE);

        HashSet<String> keywordSet = new HashSet<String>();

        if (cursor.moveToFirst()) {
            do {
                String keyword = cursor.getString(cursor.getColumnIndex(Keyword.TAG));
                if (!keywordSet.contains(keyword)) {
                    keywordSet.add(keyword);
                }
            } while (cursor.moveToNext());
        }
        cursor.close();

        Object[] keywordObjArray = keywordSet.toArray();
        ArrayList<String> keywordArray = new ArrayList<String>();

        for (Object keywordObj : keywordObjArray) {
            keywordArray.add((String) keywordObj);
        }

        Collections.sort(keywordArray);

        return keywordArray;
    }

    /**
     * 
     * @param visit
     * @return
     */
    public long insertWifiVisit(LociVisitWifi visit) {
        if (visit.enter == -1 || visit.exit == -1) {
            MyLog.e(LociConfig.D.ERROR, TAG, "[error] addWifiVisit: enter or exit is -1.");
            return -1;
        }

        if (visit.type != Places.TYPE_WIFI) {
            MyLog.e(LociConfig.D.ERROR, TAG, "[error] addWifiVisit: place type is not TYPE_WIFI. " + visit.type);
            return -1;
        }

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Visits.ENTER, visit.enter);
        args.put(Visits.EXIT, visit.exit);
        args.put(Visits.PLACE_ID, visit.placeId);
        args.put(Visits.TYPE, visit.type);
        try {
            args.put(Visits.EXTRA1, visit.wifi.toJsonObject().toString());
        } catch (JSONException e) {
            MyLog.e(LociConfig.D.JSON, TAG, "[error] addWifiVisit: json error.");
            e.printStackTrace();
            return -1;
        }
        args.put(Visits.EXTRA2, visit.getRecognitionResults());

        return db.insert(Tables.VISITS, null, args);
    }

    /**
     * 
     * @param visit Visit information to save
     * @return the number of rows affected
     */
    public int updateWifiVisit(LociVisitWifi visit) {
        if (visit.enter == -1 || visit.exit == -1) {
            MyLog.e(LociConfig.D.ERROR, TAG, "[error] updateWifiVisit: (enter or exit is -1)"
                    + String.format("enter=%d exit=%d", visit.enter, visit.exit));
            return -1;
        }

        if (visit.type != Places.TYPE_WIFI) {
            MyLog.e(LociConfig.D.ERROR, TAG, "[error] updateWifiVisit: place type is not TYPE_WIFI. " + visit.type);
            return -1;
        }

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Visits.ENTER, visit.enter);
        args.put(Visits.EXIT, visit.exit);
        args.put(Visits.PLACE_ID, visit.placeId);
        args.put(Visits.TYPE, visit.type);
        try {
            args.put(Visits.EXTRA1, visit.wifi.toJsonObject().toString());
        } catch (JSONException e) {
            MyLog.e(LociConfig.D.JSON, TAG, "[error] addWifiVisit: json error (parsing wifi to json failed).");
            e.printStackTrace();
            return -1;
        }
        args.put(Visits.EXTRA2, visit.getRecognitionResults());

        MyLog.d(LociConfig.D.DB.DEBUG, TAG,
                String.format("[DB] (updateWifiVisit) visitId=%d, enter=%s, exit=%s, placeId=%d", visit.visitId,
                        MyDateUtils.getTimeFormatMedium(visit.enter), MyDateUtils.getTimeFormatMedium(visit.exit),
                        visit.placeId));

        return db.update(Tables.VISITS, args, Visits._ID + "=" + visit.visitId, null);
    }

    /**
     * 
     * @return
     */
    public LociVisitWifi getLastWifiVisit() {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = db.query(Tables.VISITS, null, Visits.TYPE + "=" + Places.TYPE_WIFI, null, null, null,
                Visits._ID + " DESC", "1");
        ArrayList<LociVisitWifi> list = cursor2visitwifi(cursor);

        if (list != null && list.size() > 0)
            return list.get(0);
        else
            return null;
    }

    public ArrayList<LociVisitWifi> getWifiVisits(long start, long end) {
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();

        String selection = (Visits.EXIT + ">=" + start + " AND " + Visits.ENTER + "<=" + end)
                + (" AND " + Visits.TYPE + "=" + Places.TYPE_WIFI);
        Cursor cursor = db.query(Tables.VISITS, null, selection, null, null, null, null);

        return cursor2visitwifi(cursor);
    }

    /**
     * Returns visit basic information including visit_id, type, enter_time, and exit_time
     * @param placeId is place id
     * @param orderBy 
     * @param maxCount maximum number of rows to return. ignored when null.
     * @return
     */
    public ArrayList<LociVisit> getBaseVisits(long placeId, String orderBy, String maxCount) {

        ArrayList<LociVisit> visits = new ArrayList<LociVisit>();

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        String selection = (Visits.PLACE_ID + "=" + placeId);

        Cursor cursor;

        if (maxCount != null)
            cursor = db.query(Tables.VISITS, null, selection, null, null, null, orderBy, maxCount);
        else
            cursor = db.query(Tables.VISITS, null, selection, null, null, null, orderBy);

        if (cursor.moveToFirst()) {
            do {
                long visitId = cursor.getLong(cursor.getColumnIndex(Visits._ID));
                int type = cursor.getInt(cursor.getColumnIndex(Visits.TYPE));
                long enter = cursor.getLong(cursor.getColumnIndex(Visits.ENTER));
                long exit = cursor.getLong(cursor.getColumnIndex(Visits.EXIT));

                visits.add(new LociVisit(visitId, placeId, type, enter, exit));

                //Log.d(TAG, String.format("visitId=%ld type=%d enter=%d exit=%d", visitId, type, enter, exit));

            } while (cursor.moveToNext());
        }

        cursor.close();
        return visits;
    }

    public ArrayList<LociVisit> getBaseVisits(long date) {
        Calendar endTime = Calendar.getInstance();
        endTime.setTimeInMillis(date);
        endTime.add(Calendar.DAY_OF_MONTH, 1);
        return getBaseVisits(date, endTime.getTimeInMillis());
    }

    public ArrayList<LociVisit> getBaseVisits(long startTime, long endTime) {
        ArrayList<LociVisit> visits = new ArrayList<LociVisit>();

        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        String selection = (Visits.EXIT + ">=" + startTime + " AND " + Visits.ENTER + "<=" + endTime);

        Cursor cursor = db.query(Tables.VISITS, null, selection, null, null, null, null);

        if (cursor.moveToFirst()) {
            do {
                long visitId = cursor.getLong(cursor.getColumnIndex(Visits._ID));
                long placeId = cursor.getLong(cursor.getColumnIndex(Visits.PLACE_ID));
                int type = cursor.getInt(cursor.getColumnIndex(Visits.TYPE));
                long enter = cursor.getLong(cursor.getColumnIndex(Visits.ENTER));
                long exit = cursor.getLong(cursor.getColumnIndex(Visits.EXIT));

                //Log.d(TAG, String.format("visitId=%d placeId=%d type=%d enter=%d exit=%d", visitId, placeId, type, enter, exit));

                visits.add(new LociVisit(visitId, placeId, type, enter, exit));
            } while (cursor.moveToNext());
        }
        cursor.close();
        return visits;
    }

    /**
     * 
     * @param cursor
     * @return
     */
    private ArrayList<LociVisitWifi> cursor2visitwifi(Cursor cursor) {
        ArrayList<LociVisitWifi> visits = new ArrayList<LociVisitWifi>();
        if (cursor.moveToFirst()) {
            do {
                long visitId = cursor.getLong(cursor.getColumnIndex(Visits._ID));
                long placeId = cursor.getLong(cursor.getColumnIndex(Visits.PLACE_ID));
                int type = cursor.getInt(cursor.getColumnIndex(Visits.TYPE));
                long enter = cursor.getLong(cursor.getColumnIndex(Visits.ENTER));
                long exit = cursor.getLong(cursor.getColumnIndex(Visits.EXIT));

                if (type != Places.TYPE_WIFI) {
                    MyLog.e(LociConfig.D.ERROR, TAG, "cursor2visitwifi: type is not wifi.");
                    continue;
                }

                LociWifiFingerprint wifi = null;
                try {
                    wifi = new LociWifiFingerprint(cursor.getString(cursor.getColumnIndex(Visits.EXTRA1)));
                } catch (JSONException e) {
                    MyLog.e(LociConfig.D.JSON, TAG, "cursor2visitwifi : EXTRA1 (wifi fingerprint) json error.");
                }

                ArrayList<RecognitionResult> recognitions = new ArrayList<RecognitionResult>();
                try {
                    JSONArray jArr = new JSONArray(cursor.getString(cursor.getColumnIndex(Visits.EXTRA2)));
                    for (int i = 0; i < jArr.length(); i++) {
                        JSONObject jObj = jArr.getJSONObject(i);
                        RecognitionResult result = new RecognitionResult(jObj.getLong("time"),
                                jObj.getInt("place_id"), jObj.getInt("figerprint_id"), jObj.getDouble("score"));
                        recognitions.add(result);
                    }
                } catch (JSONException e) {
                    MyLog.e(LociConfig.D.JSON, TAG, "cursor2visitwifi : EXTRA2 (recog results) json error.");
                }
                LociVisitWifi visit = new LociVisitWifi(visitId, placeId, enter, exit, recognitions, wifi);
                visits.add(visit);

            } while (cursor.moveToNext());
        }
        cursor.close();
        return visits;
    }

    public int updateVisitPlaceId(long visitId, long placeId) {
        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Visits.PLACE_ID, placeId);

        MyLog.d(LociConfig.D.DB.CALL, TAG,
                String.format("[DB] change visit's placeid : (visitId=%d, placeId=%d)", visitId, placeId));

        return db.update(Tables.VISITS, args, Visits._ID + "=" + visitId, null);
    }

    public int changeVisitPlaceIdAll(long oldId, long newId) {
        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Visits.PLACE_ID, newId);

        MyLog.d(LociConfig.D.DB.CALL, TAG,
                String.format("[DB] change all visits placeid : (oldId=%d, newId=%d)", oldId, newId));

        return db.update(Tables.VISITS, args, Visits.PLACE_ID + "=" + oldId, null);
    }

    /**
     * 
     * @param time
     * @param loc
     * @return
     */
    public long insertPosition(long time, Location loc) {

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        ContentValues args = new ContentValues();
        args.put(Tracks.TIME, time);
        args.put(Tracks.LATITUDE, loc.getLatitude());
        args.put(Tracks.LONGITUDE, loc.getLongitude());
        args.put(Tracks.ALTITUDE, loc.getAltitude());
        args.put(Tracks.SPEED, loc.getSpeed());
        args.put(Tracks.ACCURACY, loc.getAccuracy());
        args.put(Tracks.BEARING, loc.getBearing());
        args.put(Tracks.SYNC, 0);

        //MyLog.d(LociConfig.Debug.Provder.DB.LOG_EVENT, TAG, "[Db] insertPosition " + String.format("lat=%7.2f, lon=%7.2f, alt=%7.2f, speed=%7.2f, acc=%7.2f", loc.getLatitude(), loc.getLongitude(), loc.getAltitude(), loc.getSpeed(), loc.getAccuracy()));

        return db.insert(Tables.TRACKS, null, args);
    }

    /**
     * 
     * @param start
     * @param end
     * @param filter
     * @return
     */
    public ArrayList<LociLocation> getTrack(long start, long end, int filter) {
        String[] columns = new String[] { Tracks._ID, Tracks.TIME, Tracks.LATITUDE, Tracks.LONGITUDE,
                Tracks.ALTITUDE, Tracks.SPEED, Tracks.BEARING, Tracks.ACCURACY };
        String selection = Tracks.TIME + ">=" + start + " AND " + Tracks.TIME + " <= " + end;

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Cursor cursor = db.query(Tables.TRACKS, columns, selection, null, null, null, null);

        ArrayList<LociLocation> track = new ArrayList<LociLocation>();

        if (cursor.moveToFirst()) {
            do {
                LociLocation loc = new LociLocation(LocationManager.GPS_PROVIDER);
                loc.setTime(cursor.getLong(cursor.getColumnIndex(Tracks.TIME)));
                loc.setLatitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LATITUDE)));
                loc.setLongitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LONGITUDE)));
                loc.setAltitude(cursor.getDouble(cursor.getColumnIndex(Tracks.ALTITUDE)));
                loc.setSpeed(cursor.getFloat(cursor.getColumnIndex(Tracks.SPEED)));
                loc.setBearing(cursor.getFloat(cursor.getColumnIndex(Tracks.BEARING)));
                loc.setAccuracy(cursor.getFloat(cursor.getColumnIndex(Tracks.ACCURACY)));

                track.add(loc);
            } while (cursor.moveToNext());
        }
        cursor.close();
        return track;

    }

    /**
     * Returns an average position fix
     * @param start beginning time
     * @param end ending time
     * @param accuracy filter position fixes with accuracy value higher than this value
     * @return average position within the provided time interval, filtered by accuracy
     */
    public LociLocation getAveragePosition(long start, long end, int accuracy) {

        LociLocation placeLoc = null;

        String[] columns = new String[] { Tracks._ID, Tracks.TIME, Tracks.LATITUDE, Tracks.LONGITUDE,
                Tracks.ALTITUDE, Tracks.SPEED, Tracks.BEARING, Tracks.ACCURACY };

        String selection = Tracks.TIME + ">=" + start + " AND " + Tracks.TIME + " <= " + end;

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Cursor cursor = db.query(Tables.TRACKS, columns, selection, null, null, null, null);

        ArrayList<LociLocation> track = new ArrayList<LociLocation>();

        MyLog.d(LociConfig.D.DB.UTILS, TAG,
                String.format("[DB] average position between %s-%s ==> #pos=%d (accuracy=%d)",
                        MyDateUtils.getTimeFormatLong(start), MyDateUtils.getTimeFormatLong(end), cursor.getCount(),
                        accuracy));

        if (cursor.moveToFirst()) {
            do {
                LociLocation loc = new LociLocation(LocationManager.GPS_PROVIDER);
                loc.setTime(cursor.getLong(cursor.getColumnIndex(Tracks.TIME)));
                loc.setLatitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LATITUDE)));
                loc.setLongitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LONGITUDE)));
                loc.setAltitude(cursor.getDouble(cursor.getColumnIndex(Tracks.ALTITUDE)));
                loc.setSpeed(cursor.getFloat(cursor.getColumnIndex(Tracks.SPEED)));
                loc.setBearing(cursor.getFloat(cursor.getColumnIndex(Tracks.BEARING)));
                loc.setAccuracy(cursor.getFloat(cursor.getColumnIndex(Tracks.ACCURACY)));
                track.add(loc);
            } while (cursor.moveToNext());

            placeLoc = LociLocation.averageLocation(track, accuracy);
        }
        cursor.close();
        return placeLoc;

    }

    public LociLocation getPlaceLocationEstimationWithBestAccuracy(long start, long end) {
        LociLocation placeLoc = null;

        String[] columns = new String[] { Tracks._ID, Tracks.TIME, Tracks.LATITUDE, Tracks.LONGITUDE,
                Tracks.ALTITUDE, Tracks.SPEED, Tracks.BEARING, Tracks.ACCURACY };

        String selection = Tracks.TIME + ">=" + start + " AND " + Tracks.TIME + " <= " + end;

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Cursor cursor = db.query(Tables.TRACKS, columns, selection, null, null, null, null);

        float minAccuracy = Float.MAX_VALUE;

        if (cursor.moveToFirst()) {
            do {
                if (minAccuracy > cursor.getFloat(cursor.getColumnIndex(Tracks.ACCURACY))) {
                    if (placeLoc == null)
                        placeLoc = new LociLocation(LocationManager.GPS_PROVIDER);
                    placeLoc.setTime(cursor.getLong(cursor.getColumnIndex(Tracks.TIME)));
                    placeLoc.setLatitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LATITUDE)));
                    placeLoc.setLongitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LONGITUDE)));
                    placeLoc.setAltitude(cursor.getDouble(cursor.getColumnIndex(Tracks.ALTITUDE)));
                    placeLoc.setSpeed(cursor.getFloat(cursor.getColumnIndex(Tracks.SPEED)));
                    placeLoc.setBearing(cursor.getFloat(cursor.getColumnIndex(Tracks.BEARING)));
                    placeLoc.setAccuracy(cursor.getFloat(cursor.getColumnIndex(Tracks.ACCURACY)));
                    minAccuracy = placeLoc.getAccuracy();
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
        return placeLoc;
    }

    /**
     * @param time
     * @param before
     * @return first location before time if before is true. Otherwise, after time. 
     *         returns null if no location is available.
     */
    public LociLocation getFirstLocationBeforeOrAfterTime(long time, boolean before) {

        LociLocation loc = null;

        String[] columns = new String[] { Tracks._ID, Tracks.TIME, Tracks.LATITUDE, Tracks.LONGITUDE,
                Tracks.ALTITUDE, Tracks.SPEED, Tracks.BEARING, Tracks.ACCURACY };

        String selection = Tracks.TIME + "<=" + time;
        String order = " DESC";

        if (!before) {
            selection = Tracks.TIME + ">=" + time;
            order = " ASC";
        }

        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Cursor cursor = db.query(Tables.TRACKS, columns, selection, null, null, null, Tracks.TIME + order, "" + 1);

        if (cursor.moveToFirst()) {
            loc = new LociLocation(LocationManager.GPS_PROVIDER);
            loc.setTime(cursor.getLong(cursor.getColumnIndex(Tracks.TIME)));
            loc.setLatitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LATITUDE)));
            loc.setLongitude(cursor.getDouble(cursor.getColumnIndex(Tracks.LONGITUDE)));
            loc.setAltitude(cursor.getDouble(cursor.getColumnIndex(Tracks.ALTITUDE)));
            loc.setSpeed(cursor.getFloat(cursor.getColumnIndex(Tracks.SPEED)));
            loc.setBearing(cursor.getFloat(cursor.getColumnIndex(Tracks.BEARING)));
            loc.setAccuracy(cursor.getFloat(cursor.getColumnIndex(Tracks.ACCURACY)));
        }
        cursor.close();
        return loc;
    }

    private long mMimeTypeIdWifi;
    private long mMimeTypeIdGps;
    private long mMimeTypeIdKeyword;

    private StringBuilder mSb = new StringBuilder();
    private String[] mSelectionArgs1 = new String[1];
    private String[] mSelectionArgs2 = new String[2];

    private boolean mSyncToNetwork;

    private interface DataUpdateQuery {
        String[] COLUMNS = { Data._ID, Data.PLACE_ID, Data.MIMETYPE };

        int _ID = 0;
        int PLACE_ID = 1;
        int MIMETYPE = 2;
    }

    private interface DataDeleteQuery {
        public static final String TABLE = Tables.DATA_JOIN_MIMETYPES;

        public static final String[] CONCRETE_COLUMNS = new String[] { MimetypesColumns.MIMETYPE, Data.PLACE_ID,
                Data.DATA1, };

        public static final String[] COLUMNS = new String[] { Data._ID, MimetypesColumns.MIMETYPE, Data.PLACE_ID,
                Data.DATA1, };

        public static final int _ID = 0;
        public static final int MIMETYPE = 1;
        public static final int PLACE_ID = 2;
        public static final int DATA1 = 3;
    }

    public abstract class DataRowHandler {
        protected final String mMimetype;
        protected long mMimetypeId;

        public DataRowHandler(String mimetype) {
            mMimetype = mimetype;
        }

        protected long getMimeTypeId() {
            if (mMimetypeId == 0)
                mMimetypeId = mDbHelper.getMimeTypeId(mMimetype);
            return mMimetypeId;
        }

        public long insert(SQLiteDatabase db, long placeId, ContentValues values) {

            final long dataId = db.insert(Tables.DATA, null, values);
            return dataId;
        }

        public boolean update(SQLiteDatabase db, ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
            long dataId = c.getLong(DataUpdateQuery._ID);
            long placeId = c.getLong(DataUpdateQuery.PLACE_ID);

            if (values.size() > 0) {
                mSelectionArgs1[0] = String.valueOf(dataId);
                db.update(Tables.DATA, values, Data._ID + "=?", mSelectionArgs1);
            }

            if (!callerIsSyncAdapter) {
                setPlaceDirty(placeId);
            }

            return true;
        }

        public int delete(SQLiteDatabase db, Cursor c) {
            long dataId = c.getLong(DataDeleteQuery._ID);
            //long placeId = c.getLong(DataDeleteQuery.PLACE_ID);
            mSelectionArgs1[0] = String.valueOf(dataId);
            int count = db.delete(Tables.DATA, Data._ID + "=?", mSelectionArgs1);
            return count;
        }

    }

    public class CustomDataRowHandler extends DataRowHandler {

        public CustomDataRowHandler(String mimetype) {
            super(mimetype);
        }
    }

    public class WifiFingerprintRowHandler extends DataRowHandler {
        public WifiFingerprintRowHandler() {
            super(WifiFingerprint.CONTENT_ITEM_TYPE);
        }

        @Override
        public long insert(SQLiteDatabase db, long placeId, ContentValues values) {

            long dataId = super.insert(db, placeId, values);

            return dataId;
        }

        @Override
        public boolean update(SQLiteDatabase db, ContentValues values, Cursor c, boolean callerIsSyncAdapter) {

            super.update(db, values, c, callerIsSyncAdapter);

            return true;
        }

        @Override
        public int delete(SQLiteDatabase db, Cursor c) {
            int count = super.delete(db, c);

            return count;
        }
    }

    public class GpsAreaRowHandler extends DataRowHandler {
        public GpsAreaRowHandler() {
            super(GpsCircleArea.CONTENT_ITEM_TYPE);
        }

        @Override
        public long insert(SQLiteDatabase db, long placeId, ContentValues values) {
            long dataId = super.insert(db, placeId, values);

            return dataId;
        }

        @Override
        public boolean update(SQLiteDatabase db, ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
            super.update(db, values, c, callerIsSyncAdapter);
            return true;
        }

        @Override
        public int delete(SQLiteDatabase db, Cursor c) {
            int count = super.delete(db, c);
            return count;
        }
    }

    public class KeywordRowHandler extends DataRowHandler {
        public KeywordRowHandler() {
            super(Keyword.CONTENT_ITEM_TYPE);
        }

        @Override
        public long insert(SQLiteDatabase db, long placeId, ContentValues values) {
            long dataId = super.insert(db, placeId, values);

            return dataId;
        }

        @Override
        public boolean update(SQLiteDatabase db, ContentValues values, Cursor c, boolean callerIsSyncAdapter) {
            super.update(db, values, c, callerIsSyncAdapter);
            return true;
        }

        @Override
        public int delete(SQLiteDatabase db, Cursor c) {
            int count = super.delete(db, c);

            return count;
        }
    }

    private void setPlaceDirty(long rawContactId) {
        mDirtyPlaces.add(rawContactId);
    }

}