package net.osmand.plus.helpers; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.api.SQLiteAPI.SQLiteConnection; import net.osmand.plus.api.SQLiteAPI.SQLiteCursor; import net.osmand.util.Algorithms; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; public class SearchHistoryHelper { private static final int HISTORY_LIMIT = 1500; private OsmandApplication context; private List<HistoryEntry> loadedEntries = null; private Map<PointDescription, HistoryEntry> mp = new HashMap<PointDescription, SearchHistoryHelper.HistoryEntry>(); public SearchHistoryHelper(OsmandApplication context) { this.context = context; } private static SearchHistoryHelper instance = null; public static SearchHistoryHelper getInstance(OsmandApplication context){ if(instance == null) { instance = new SearchHistoryHelper(context); } return instance; } private static final int[] DEF_INTERVALS_MIN = new int[] { 5, 60, 60 * 24, 5 * 60 * 24, 10 * 60 * 24, 30 * 60 * 24 }; private static class HistoryEntryComparator implements Comparator<HistoryEntry> { long time = System.currentTimeMillis(); @Override public int compare(HistoryEntry lhs, HistoryEntry rhs) { double l = lhs.getRank(time); double r = rhs.getRank(time); return -Double.compare(l, r); } }; public static class HistoryEntry { double lat; double lon; PointDescription name; private long lastAccessedTime; private int[] intervals = new int[0]; private double[] intervalValues = new double[0]; public HistoryEntry(double lat, double lon, PointDescription name){ this.lat = lat; this.lon = lon; this.name = name; } private double rankFunction(double cf, double timeDiff) { if(timeDiff <= 0) { return 0; } return cf / timeDiff; } public double getRank(long time) { double baseTimeDiff = ((time - lastAccessedTime) / 1000) + 1; double timeDiff = 0; double vl = 1; double rnk = rankFunction(vl, baseTimeDiff + timeDiff); for (int k = 0; k < intervals.length; k++) { double ntimeDiff = intervals[k] * 60 * 1000; double nvl = intervalValues[k]; if(ntimeDiff < timeDiff || nvl <= vl){ continue; } rnk += rankFunction(nvl - vl, baseTimeDiff + (ntimeDiff - timeDiff) / 2 + timeDiff); vl = nvl - vl; timeDiff = ntimeDiff; } return rnk; } public PointDescription getName() { return name; } public String getSerializedName() { return PointDescription.serializeToString(name); } public double getLat() { return lat; } public double getLon() { return lon; } public void markAsAccessed(long time) { int[] nintervals = new int[DEF_INTERVALS_MIN.length]; double[] nintervalValues = new double[DEF_INTERVALS_MIN.length]; for(int k = 0; k < nintervals.length; k++) { nintervals[k] = DEF_INTERVALS_MIN[k]; nintervalValues[k] = getUsageLastTime(time, 0, 0, nintervals[k]) + 1; } intervals = nintervals; intervalValues = nintervalValues; this.lastAccessedTime = time; } public double getUsageLastTime(long time, int days, int hours, int minutes) { long mins = (minutes + (hours + 24 * days) * 60); long timeInPast = time - mins * 60 * 1000; if (this.lastAccessedTime <= timeInPast) { return 0; } double res = 0; for (int k = 0; k < intervals.length; k++) { long intPast = intervals[k] * 60 * 1000; if (intPast > 0) { double r; if (lastAccessedTime - timeInPast >= intPast) { r = intervalValues[k]; } else { r = intervalValues[k] * ((double) lastAccessedTime - timeInPast) / ((double) intPast); } res = Math.max(res, r); } } return res; } public void setFrequency(String intervalsString, String values) { if(Algorithms.isEmpty(intervalsString) || Algorithms.isEmpty(values)) { markAsAccessed(this.lastAccessedTime); return; } String[] ints = intervalsString.split(","); String[] vsl = values.split(","); intervals = new int[ints.length]; intervalValues = new double[ints.length]; try { for(int i = 0; i < ints.length && i < vsl.length; i++) { intervals[i] = Integer.parseInt(ints[i]); intervalValues[i] = Double.parseDouble(vsl[i]); } } catch (NumberFormatException e) { e.printStackTrace(); } } public long getLastAccessTime() { return lastAccessedTime; } public String getIntervalsValues() { StringBuilder s = new StringBuilder(); for(int i = 0; i < intervalValues.length; i++) { if(i > 0) { s.append(","); } s.append(intervalValues[i]); } return s.toString(); } public String getIntervals() { StringBuilder s = new StringBuilder(); for(int i = 0; i < intervals.length; i++) { if(i > 0) { s.append(","); } s.append(intervals[i]); } return s.toString(); } public void setLastAccessTime(long time) { this.lastAccessedTime = time; } } public List<HistoryEntry> getHistoryEntries() { if(loadedEntries == null){ checkLoadedEntries(); } return new ArrayList<SearchHistoryHelper.HistoryEntry>(loadedEntries); } private HistoryItemDBHelper checkLoadedEntries() { HistoryItemDBHelper helper = new HistoryItemDBHelper(); if (loadedEntries == null) { loadedEntries = helper.getEntries(); Collections.sort(loadedEntries, new HistoryEntryComparator()); for(HistoryEntry he : loadedEntries) { mp.put(he.getName(), he); } } return helper; } public void remove(HistoryEntry model) { HistoryItemDBHelper helper = checkLoadedEntries(); if (helper.remove(model)) { loadedEntries.remove(model); mp.remove(model.getName()); } } public void removeAll() { HistoryItemDBHelper helper = checkLoadedEntries(); if (helper.removeAll()) { loadedEntries.clear(); mp.clear(); } } public void addNewItemToHistory(HistoryEntry model) { HistoryItemDBHelper helper = checkLoadedEntries(); if(mp.containsKey(model.getName())) { model = mp.get(model.getName()); model.markAsAccessed(System.currentTimeMillis()); helper.update(model); } else { loadedEntries.add(model); mp.put(model.getName(), model); model.markAsAccessed(System.currentTimeMillis()); helper.add(model); } Collections.sort(loadedEntries, new HistoryEntryComparator()); if(loadedEntries.size() > HISTORY_LIMIT){ if(helper.remove(loadedEntries.get(loadedEntries.size() - 1))){ loadedEntries.remove(loadedEntries.size() - 1); } } } private class HistoryItemDBHelper { private static final String DB_NAME = "search_history"; //$NON-NLS-1$ private static final int DB_VERSION = 2; private static final String HISTORY_TABLE_NAME = "history_recents"; //$NON-NLS-1$ private static final String HISTORY_COL_NAME = "name"; //$NON-NLS-1$ private static final String HISTORY_COL_TIME = "time"; //$NON-NLS-1$ private static final String HISTORY_COL_FREQ_INTERVALS = "freq_intervals"; //$NON-NLS-1$ private static final String HISTORY_COL_FREQ_VALUES = "freq_values"; //$NON-NLS-1$ private static final String HISTORY_COL_LAT = "latitude"; //$NON-NLS-1$ private static final String HISTORY_COL_LON = "longitude"; //$NON-NLS-1$ private static final String HISTORY_TABLE_CREATE = "CREATE TABLE IF NOT EXISTS " + HISTORY_TABLE_NAME + " (" + //$NON-NLS-1$ //$NON-NLS-2$ HISTORY_COL_NAME + " TEXT, " + HISTORY_COL_TIME + " long, " + HISTORY_COL_FREQ_INTERVALS + " TEXT, " + HISTORY_COL_FREQ_VALUES + " TEXT, " + HISTORY_COL_LAT + " double, " +HISTORY_COL_LON + " double);"; //$NON-NLS-1$ //$NON-NLS-2$ public HistoryItemDBHelper() { } private SQLiteConnection openConnection(boolean readonly) { SQLiteConnection conn = context.getSQLiteAPI().getOrCreateDatabase(DB_NAME, readonly); if (conn.getVersion() == 0 || DB_VERSION != conn.getVersion()) { if (readonly) { conn.close(); conn = context.getSQLiteAPI().getOrCreateDatabase(DB_NAME, readonly); } if (conn.getVersion() == 0) { onCreate(conn); } else { onUpgrade(conn, conn.getVersion(), DB_VERSION); } conn.setVersion(DB_VERSION); } return conn; } public void onCreate(SQLiteConnection db) { db.execSQL(HISTORY_TABLE_CREATE); } public void onUpgrade(SQLiteConnection db, int oldVersion, int newVersion) { if(newVersion == 2) { db.execSQL(HISTORY_TABLE_CREATE); for(HistoryEntry he : getLegacyEntries(db)) { insert(he, db); } } } public boolean remove(HistoryEntry e){ SQLiteConnection db = openConnection(false); if(db != null){ try { removeQuery(e.getSerializedName(), db); } finally { db.close(); } return true; } return false; } private void removeQuery(String name, SQLiteConnection db) { db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME + " WHERE " + HISTORY_COL_NAME + " = ?", new Object[] { name }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } public boolean removeAll(){ SQLiteConnection db = openConnection(false); if(db != null){ try { db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME); //$NON-NLS-1$ } finally { db.close(); } return true; } return false; } public boolean update(HistoryEntry e){ SQLiteConnection db = openConnection(false); if(db != null){ try { db.execSQL( "UPDATE " + HISTORY_TABLE_NAME + " SET " + HISTORY_COL_TIME + "= ? "+ ", " + HISTORY_COL_FREQ_INTERVALS + " = ? " + ", " +HISTORY_COL_FREQ_VALUES + "= ? WHERE " + HISTORY_COL_NAME + " = ?", new Object[] { e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(), e.getSerializedName() }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } finally { db.close(); } return true; } return false; } public boolean add(HistoryEntry e){ SQLiteConnection db = openConnection(false); if(db != null){ try { insert(e, db); } finally { db.close(); } return true; } return false; } private void insert(HistoryEntry e, SQLiteConnection db) { db.execSQL( "INSERT INTO " + HISTORY_TABLE_NAME + " VALUES (?, ?, ?, ?, ?, ?)", new Object[] { e.getSerializedName(), e.getLastAccessTime(), e.getIntervals(), e.getIntervalsValues(), e.getLat(), e.getLon() }); //$NON-NLS-1$ //$NON-NLS-2$ } public List<HistoryEntry> getLegacyEntries(SQLiteConnection db){ List<HistoryEntry> entries = new ArrayList<HistoryEntry>(); if (db != null) { // LEGACY QUERY !! SQLiteCursor query = db.rawQuery( "SELECT name, latitude, longitude, time FROM history ORDER BY time DESC", null); //$NON-NLS-1$//$NON-NLS-2$ if (query.moveToFirst()) { do { String name = query.getString(0); String type = PointDescription.POINT_TYPE_MARKER; // make it proper name with type if (name.contains(context.getString(R.string.favorite))) { type = PointDescription.POINT_TYPE_FAVORITE; } else if (name.contains(context.getString(R.string.search_address_building))) { type = PointDescription.POINT_TYPE_ADDRESS; } else if (name.contains(context.getString(R.string.search_address_city))) { type = PointDescription.POINT_TYPE_ADDRESS; } else if (name.contains(context.getString(R.string.search_address_street))) { type = PointDescription.POINT_TYPE_ADDRESS; } else if (name.contains(context.getString(R.string.search_address_street_option))) { type = PointDescription.POINT_TYPE_ADDRESS; } else if (name.contains(context.getString(R.string.poi))) { type = PointDescription.POINT_TYPE_POI; } if (name.contains(":")) { name = name.substring(name.indexOf(':') + 1); } HistoryEntry e = new HistoryEntry(query.getDouble(1), query.getDouble(2), new PointDescription( type, name)); e.markAsAccessed(query.getLong(3)); entries.add(e); } while (query.moveToNext()); } query.close(); } return entries; } public List<HistoryEntry> getEntries(){ List<HistoryEntry> entries = new ArrayList<HistoryEntry>(); SQLiteConnection db = openConnection(true); if(db != null){ try { SQLiteCursor query = db.rawQuery( "SELECT " + HISTORY_COL_NAME + ", " + HISTORY_COL_LAT + "," + HISTORY_COL_LON +", " + HISTORY_COL_TIME + ", " + HISTORY_COL_FREQ_INTERVALS + ", " + HISTORY_COL_FREQ_VALUES + " FROM " + HISTORY_TABLE_NAME , null); //$NON-NLS-1$//$NON-NLS-2$ Map<PointDescription, HistoryEntry> st = new HashMap<PointDescription, HistoryEntry>(); if (query.moveToFirst()) { boolean reinsert = false; do { String name = query.getString(0); PointDescription p = PointDescription.deserializeFromString(name, new LatLon(query.getDouble(1), query.getDouble(2))); HistoryEntry e = new HistoryEntry(query.getDouble(1), query.getDouble(2), p); long time = query.getLong(3); e.setLastAccessTime(time); e.setFrequency(query.getString(4), query.getString(5)); if(st.containsKey(p)) { reinsert = true; } entries.add(e); st.put(p, e); } while (query.moveToNext()); if(reinsert) { System.err.println("Reinsert all values for search history"); db.execSQL("DELETE FROM " + HISTORY_TABLE_NAME); //$NON-NLS-1$ entries.clear(); entries.addAll(st.values()); for(HistoryEntry he : entries) { insert(he, db); } } } query.close(); } finally { db.close(); } } return entries; } } public void addNewItemToHistory(double latitude, double longitude, PointDescription pointDescription) { addNewItemToHistory(new HistoryEntry(latitude, longitude, pointDescription)); } }