package me.guillaumin.android.osmtracker.db;
import java.io.File;
import java.text.SimpleDateFormat;
import me.guillaumin.android.osmtracker.OSMTracker;
import me.guillaumin.android.osmtracker.db.TrackContentProvider.Schema;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Data helper for dialoging with content resolver and filesystem.
*
* @author Nicolas Guillaumin
*
*/
public class DataHelper {
private static final String TAG = DataHelper.class.getSimpleName();
/**
* GPX file extension.
*/
public static final String EXTENSION_GPX = ".gpx";
/**
* 3GPP extension
*/
public static final String EXTENSION_3GPP = ".3gpp";
/**
* JPG file extension
*/
public static final String EXTENSION_JPG = ".jpg";
/**
* Number of tries to rename a media file for the current track if there are
* already a media file of this name.
*/
private static final int MAX_RENAME_ATTEMPTS = 20;
/**
* valid range for azimuth angles.
*/
public static final float AZIMUTH_MIN = 0;
public static final float AZIMUTH_MAX = 360;
public static final float AZIMUTH_INVALID = -1;
/**
* Formatter for various files (GPX, media)
*/
public static final SimpleDateFormat FILENAME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
/**
* Context
*/
private Context context;
/**
* ContentResolver to interact with content provider
*/
private ContentResolver contentResolver;
/**
* Constructor.
*
* @param c
* Application context.
*/
public DataHelper(Context c) {
context = c;
contentResolver = c.getContentResolver();
}
/**
* Track a point into DB.
*
* @param trackId
* Id of the track
* @param location
* The Location to track
* @param azimuth
* azimuth angle in degrees (0-360deg) of the track point. if it is outside the given range it will be set null.
* @param accuracy
* accuracy of the compass reading (as SensorManager.SENSOR_STATUS_ACCURACY*),
* ignored if azimuth is invalid.
*/
public void track(long trackId, Location location, float azimuth, int accuracy) {
Log.v(TAG, "Tracking (trackId=" + trackId + ") location: " + location + " azimuth: " + azimuth + ", accuracy: " + accuracy);
ContentValues values = new ContentValues();
values.put(Schema.COL_TRACK_ID, trackId);
values.put(Schema.COL_LATITUDE, location.getLatitude());
values.put(Schema.COL_LONGITUDE, location.getLongitude());
if (location.hasAltitude()) {
values.put(Schema.COL_ELEVATION, location.getAltitude());
}
if (location.hasAccuracy()) {
values.put(Schema.COL_ACCURACY, location.getAccuracy());
}
if (location.hasSpeed()) {
values.put(Schema.COL_SPEED, location.getSpeed());
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean(OSMTracker.Preferences.KEY_GPS_IGNORE_CLOCK, OSMTracker.Preferences.VAL_GPS_IGNORE_CLOCK)) {
// Use OS clock
values.put(Schema.COL_TIMESTAMP, System.currentTimeMillis());
} else {
// Use GPS clock
values.put(Schema.COL_TIMESTAMP, location.getTime());
}
if (azimuth >= AZIMUTH_MIN && azimuth < AZIMUTH_MAX) {
values.put(Schema.COL_COMPASS, azimuth);
values.put(Schema.COL_COMPASS_ACCURACY, accuracy);
}
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
contentResolver.insert(Uri.withAppendedPath(trackUri, Schema.TBL_TRACKPOINT + "s"), values);
}
/**
* Tracks a way point with link
*
* @param trackId
* Id of the track
* @param location
* Location of waypoint
* @param nbSatellites
* Number of satellites used for the location
* @param name
* Name of waypoint
* @param link
* Link of waypoint
* @param uuid
* Unique id of the waypoint
* @param azimuth
* azimuth angle in degrees (0-360deg) of the way point. if it is outside the given range it will be set null.
* @param accuracy
* accuracy of the compass reading (as SensorManager.SENSOR_STATUS_ACCURACY*),
* ignored if azimuth is invalid.
*/
public void wayPoint(long trackId, Location location, int nbSatellites, String name, String link, String uuid, float azimuth, int accuracy) {
Log.v(TAG, "Tracking waypoint '" + name + "', track=" + trackId + ", uuid=" + uuid + ", link='" + link + "', location=" + location + ", azimuth=" + azimuth + ", accuracy="+accuracy);
// location should not be null, but sometime is.
// TODO investigate this issue.
if (location != null) {
ContentValues values = new ContentValues();
values.put(Schema.COL_TRACK_ID, trackId);
values.put(Schema.COL_LATITUDE, location.getLatitude());
values.put(Schema.COL_LONGITUDE, location.getLongitude());
values.put(Schema.COL_NAME, name);
values.put(Schema.COL_NBSATELLITES, nbSatellites);
if (uuid != null) {
values.put(Schema.COL_UUID, uuid);
}
if (location.hasAltitude()) {
values.put(Schema.COL_ELEVATION, location.getAltitude());
}
if (location.hasAccuracy()) {
values.put(Schema.COL_ACCURACY, location.getAccuracy());
}
if (link != null) {
// Rename file to match location timestamp
values.put(Schema.COL_LINK, renameFile(trackId, link, FILENAME_FORMATTER.format(location.getTime())));
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean(OSMTracker.Preferences.KEY_GPS_IGNORE_CLOCK, OSMTracker.Preferences.VAL_GPS_IGNORE_CLOCK)) {
// Use OS clock
values.put(Schema.COL_TIMESTAMP, System.currentTimeMillis());
} else {
// Use GPS clock
values.put(Schema.COL_TIMESTAMP, location.getTime());
}
//add compass if valid
if (azimuth >= AZIMUTH_MIN && azimuth < AZIMUTH_MAX) {
values.put(Schema.COL_COMPASS, azimuth);
values.put(Schema.COL_COMPASS_ACCURACY, accuracy);
}
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
contentResolver.insert(Uri.withAppendedPath(trackUri, Schema.TBL_WAYPOINT + "s"), values);
}
}
/**
* Updates a waypoint
*
* @param trackId
* Id of the track
* @param uuid
* Unique ID of the target waypoint
* @param name
* New name
* @param link
* New link
*/
public void updateWayPoint(long trackId, String uuid, String name, String link) {
Log.v(TAG, "Updating waypoint with uuid '" + uuid + "'. New values: name='" + name + "', link='" + link + "'");
if (uuid != null) {
ContentValues values = new ContentValues();
if (name != null) {
values.put(Schema.COL_NAME, name);
}
if (link != null) {
values.put(Schema.COL_LINK, link);
}
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
contentResolver.update(Uri.withAppendedPath(trackUri, Schema.TBL_WAYPOINT + "s"), values,
"uuid = ?", new String[] { uuid });
}
}
/**
* Deletes a waypoint
*
* @param uuid
* Unique ID of the target waypoint
*/
public void deleteWayPoint(String uuid) {
Log.v(TAG, "Deleting waypoint with uuid '" + uuid);
if (uuid != null) {
contentResolver.delete(Uri.withAppendedPath(TrackContentProvider.CONTENT_URI_WAYPOINT_UUID, uuid), null, null);
}
}
/**
* Stop tracking by making the track inactive
* @param trackId Id of the track
*/
public void stopTracking(long trackId) {
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
ContentValues values = new ContentValues();
values.put(Schema.COL_ACTIVE, Schema.VAL_TRACK_INACTIVE);
contentResolver.update(trackUri, values, null, null);
}
/**
* Find the active track ID, if any.
* @param cr {@link ContentResolver} for query
* @return the active track ID, or -1
*/
public static long getActiveTrackId(ContentResolver cr) {
long currentTrackId = -1;
Cursor ca = cr.query(TrackContentProvider.CONTENT_URI_TRACK_ACTIVE, null, null, null, null);
if (ca.moveToFirst()) {
currentTrackId = ca.getLong(ca.getColumnIndex(Schema.COL_ID));
}
ca.close();
return currentTrackId;
}
/**
* Change the name of this track.
* @param trackId Id of the track
* @param name New name of track, or null to clear it
* @param cr Database connection for query
*/
public static void setTrackName(long trackId, String name, ContentResolver cr) {
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
ContentValues values = new ContentValues();
values.put(Schema.COL_NAME, name);
cr.update(trackUri, values, null, null);
}
/**
* Mark the export date/time of this track.
* @param trackId Id of the track
* @param exportTime Time of export, from {@link System#currentTimeMillis()}
* @param cr {@link ContentResolver} for query
*/
public static void setTrackExportDate(long trackId, long exportTime, ContentResolver cr) {
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
ContentValues values = new ContentValues();
values.put(Schema.COL_EXPORT_DATE, exportTime);
cr.update(trackUri, values, null, null);
}
public static void setTrackUploadDate(long trackId, long uploadTime, ContentResolver cr) {
Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId);
ContentValues values = new ContentValues();
values.put(Schema.COL_OSM_UPLOAD_DATE, uploadTime);
cr.update(trackUri, values, null, null);
}
/**
* Renames a file inside track directory, keeping the extension
*
* @param from
* File to rename (Ex: "abc.png")
* @param to
* Filename to use for new name (Ex: "def")
* @return Renamed filename (Ex: "def.png")
*/
private String renameFile(Long trackId, String from, String to) {
// If all goes terribly wrong and we can't rename the file,
// we will return the original file name we were given
String _return = from;
File trackDir = getTrackDirectory(trackId);
String ext = from.substring(from.lastIndexOf(".") + 1, from.length());
File origin = new File(trackDir + File.separator + from);
// No point in trying to rename the file unless it exist
if (origin.exists()) {
File target = new File(trackDir + File.separator + to + "." + ext);
// Check & manages if there is already a file with this name
for (int i = 0; i < MAX_RENAME_ATTEMPTS && target.exists(); i++) {
target = new File(trackDir + File.separator + to + i + "." + ext);
}
origin.renameTo(target);
_return = target.getName();
}
return _return;
}
/**
* @param cr Content Resolver to use
* @param trackId Track id
* @return A File to the track directory for the target track id.
*/
public static File getTrackDirFromDB(ContentResolver cr, long trackId) {
File trackDir = null;
Cursor c = cr.query(
ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId),
null, null, null, null);
if (c != null && c.getCount() != 0) {
c.moveToFirst();
@SuppressWarnings("deprecation")
String trackPath = c.getString(c.getColumnIndex(Schema.COL_DIR));
if (trackPath != null) {
trackDir = new File(trackPath);
}
}
if (c != null && !c.isClosed()) {
c.close();
c = null;
}
return trackDir;
}
/**
* Generate a string of the directory path to external storage for the track id provided
* @param trackId Track id
* @return A the path where this track should store its files
*/
public static File getTrackDirectory(long trackId) {
File _return = null;
String trackStorageDirectory = Environment.getExternalStorageDirectory()
+ "/osmtracker/data/files/track" + trackId;
_return = new File(trackStorageDirectory);
return _return;
}
}