From 7853e24671227b4f4d3fdc2e9d152669a5d03a33 Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Wed, 4 Jun 2014 14:58:22 +0200 Subject: [PATCH] Update gpx utilities, small fixes osmo --- OsmAnd/res/layout-land/menu.xml | 2 +- OsmAnd/res/layout-large-land/menu.xml | 2 +- OsmAnd/res/layout-large/menu.xml | 2 +- OsmAnd/res/layout/menu.xml | 2 +- OsmAnd/res/layout/tab_content.xml | 32 + OsmAnd/res/values/strings.xml | 2 + OsmAnd/src/net/osmand/plus/GPXUtilities.java | 323 +++++++- .../plus/activities/FavouritesActivity.java | 19 + .../activities/FavouritesTreeFragment.java | 701 ++++++++++++++++++ .../OsmandExpandableListFragment.java | 52 ++ .../net/osmand/plus/helpers/GpxUiHelper.java | 134 +--- .../osmand/plus/osmo/OsMoControlDevice.java | 12 +- .../osmand/plus/osmo/OsMoPositionLayer.java | 53 +- .../src/net/osmand/plus/osmo/OsMoTracker.java | 40 +- plugins/Osmand-Sherpafy/AndroidManifest.xml | 2 +- 15 files changed, 1219 insertions(+), 159 deletions(-) create mode 100644 OsmAnd/res/layout/tab_content.xml create mode 100644 OsmAnd/src/net/osmand/plus/activities/FavouritesTreeFragment.java create mode 100644 OsmAnd/src/net/osmand/plus/activities/OsmandExpandableListFragment.java diff --git a/OsmAnd/res/layout-land/menu.xml b/OsmAnd/res/layout-land/menu.xml index 6cff01c1db..752d96613e 100644 --- a/OsmAnd/res/layout-land/menu.xml +++ b/OsmAnd/res/layout-land/menu.xml @@ -62,7 +62,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:b + android:text="@string/my_data_Button" android:typeface="serif" android:textColor="#000000"/> diff --git a/OsmAnd/res/layout/menu.xml b/OsmAnd/res/layout/menu.xml index 24da0231d0..2ff8192857 100644 --- a/OsmAnd/res/layout/menu.xml +++ b/OsmAnd/res/layout/menu.xml @@ -60,7 +60,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:b + android:text="@string/my_data_Button" android:typeface="serif" android:textColor="#000000"/> diff --git a/OsmAnd/res/layout/tab_content.xml b/OsmAnd/res/layout/tab_content.xml new file mode 100644 index 0000000000..99e54ecbc8 --- /dev/null +++ b/OsmAnd/res/layout/tab_content.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 48a91d46d8..6b56ed4dce 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -9,6 +9,8 @@ 3. All your modified/created strings are in the top of the file (to make easier find what\'s translated). PLEASE: Have a look at http://code.google.com/p/osmand/wiki/UIConsistency, it may really improve your and our work :-) Thx - Hardy --> + My Data + My Data User %1$s joined group %2$s User %1$s left group %2$s Show group notifications diff --git a/OsmAnd/src/net/osmand/plus/GPXUtilities.java b/OsmAnd/src/net/osmand/plus/GPXUtilities.java index 81bbc3822a..b47737716b 100644 --- a/OsmAnd/src/net/osmand/plus/GPXUtilities.java +++ b/OsmAnd/src/net/osmand/plus/GPXUtilities.java @@ -108,7 +108,236 @@ public class GPXUtilities { public List points = new ArrayList(); } + + public static class GPXTrackAnalysis { + public float totalDistance = 0; + public int totalTracks = 0; + public long startTime = Long.MAX_VALUE; + public long endTime = Long.MIN_VALUE; + public long timeSpan = 0; + public long timeMoving = 0; + public float totalDistanceMoving = 0; + public double diffElevationUp = 0; + public double diffElevationDown = 0; + public double avgElevation = 0; + public double minElevation = 99999; + public double maxElevation = -100; + + public float maxSpeed = 0; + public float avgSpeed; + + public int points; + public int wptPoints = 0; + + public int metricEnd; + + public boolean isTimeSpecified() { + return startTime != Long.MAX_VALUE; + } + + public boolean isTimeMoving() { + return timeMoving != 0; + } + + public boolean isElevationSpecified() { + return maxElevation != -100; + } + + public int getTimeHours(long time) { + return (int) ((time / 1000) / 3600); + } + + public int getTimeSeconds(long time) { + return (int) ((time / 1000) % 60); + } + + public int getTimeMinutes(long time) { + return (int) (((time / 1000) / 60) % 60); + } + + public boolean isSpeedSpecified() { + return avgSpeed > 0; + } + + + + public void prepareInformation(long filestamp, SplitSegment... splitSegments) { + float[] calculations = new float[1]; + + float totalElevation = 0; + int elevationPoints = 0; + int speedCount = 0; + double totalSpeedSum = 0; + points = 0; + for (SplitSegment s : splitSegments) { + final int numberOfPoints = s.getNumberOfPoints(); + metricEnd += s.metricEnd; + points += numberOfPoints; + for (int j = 0; j < numberOfPoints; j++) { + WptPt point = s.get(j); + long time = point.time; + if (time != 0) { + startTime = Math.min(startTime, time); + endTime = Math.max(startTime, time); + } + + double elevation = point.ele; + if (!Double.isNaN(elevation)) { + totalElevation += elevation; + elevationPoints++; + minElevation = Math.min(elevation, minElevation); + maxElevation = Math.max(elevation, maxElevation); + } + + float speed = (float) point.speed; + if (speed > 0) { + totalSpeedSum += speed; + maxSpeed = Math.max(speed, maxSpeed); + speedCount++; + } + + if (j > 0) { + WptPt prev = s.get(j - 1); + + if (!Double.isNaN(point.ele) && !Double.isNaN(prev.ele)) { + double diff = point.ele - prev.ele; + if (diff > 0) { + diffElevationUp += diff; + } else { + diffElevationDown -= diff; + } + } + + // totalDistance += MapUtils.getDistance(prev.lat, prev.lon, point.lat, point.lon); + // using ellipsoidal 'distanceBetween' instead of spherical haversine (MapUtils.getDistance) is + // a little more exact, also seems slightly faster: + net.osmand.Location.distanceBetween(prev.lat, prev.lon, point.lat, point.lon, calculations); + totalDistance += calculations[0]; + + // Averaging speed values is less exact than totalDistance/timeMoving + if (speed > 0 && point.time != 0 && prev.time != 0) { + timeMoving = timeMoving + (point.time - prev.time); + totalDistanceMoving += calculations[0]; + } + } + } + } + if(!isTimeSpecified()){ + startTime = filestamp; + endTime = filestamp; + } + + // OUTPUT: + // 1. Total distance, Start time, End time + // 2. Time span + timeSpan = endTime - startTime; + + // 3. Time moving, if any + if (elevationPoints > 0) { + avgElevation = elevationPoints / totalElevation; + } + + // 4. Elevation, eleUp, eleDown, if recorded + + // 5. Max speed and Average speed, if any. Average speed is NOT overall (effective) speed, but only calculated for "moving" periods. + if(speedCount > 0) { + if(timeMoving > 0){ + avgSpeed = (float) (totalDistanceMoving / timeMoving * 1000); + } else { + avgSpeed = (float) (totalSpeedSum / speedCount); + } + } else { + avgSpeed = -1; + } + } + } + + private static class SplitSegment { + TrkSegment segment; + float startCoeff = 0; + int startPointInd; + float endCoeff = 0; + int endPointInd; + int metricEnd; + + public int getNumberOfPoints() { + return endPointInd - startPointInd + 2; + } + + public WptPt get(int j) { + final int ind = j + startPointInd; + if(j == 0) { + if(startCoeff == 0) { + return segment.points.get(ind); + } + return approx(segment.points.get(ind), segment.points.get(ind + 1), startCoeff); + } + if(j == getNumberOfPoints()) { + if(startCoeff == 1) { + return segment.points.get(ind); + } + return approx(segment.points.get(ind - 1), segment.points.get(ind), endCoeff); + } + return segment.points.get(ind); + } + + + private WptPt approx(WptPt w1, WptPt w2, float cf) { + long time = value(w1.time, w2.time, 0, cf); + double speed = value(w1.speed, w2.speed, 0, cf); + double ele = value(w1.ele, w2.ele, 0, cf); + double hdop = value(w1.hdop, w2.hdop, 0, cf); + double lat = value(w1.lat, w2.lat, -360, cf); + double lon = value(w1.lon, w2.lon, -360, cf); + return new WptPt(lat, lon, time, ele, speed, hdop); + } + + private double value(double vl, double vl2, double none, float cf) { + if(vl == none || Double.isNaN(vl)) { + return vl2; + } else if (vl2 == none || Double.isNaN(vl2)) { + return vl; + } + return vl + cf * (vl2 - vl); + } + + private long value(long vl, long vl2, long none, float cf) { + if(vl == none) { + return vl2; + } else if(vl2 == none) { + return vl; + } + return vl + ((long) cf * (vl2 - vl)); + } + + public SplitSegment(TrkSegment s) { + startPointInd = 0; + startCoeff = 0; + endPointInd = s.points.size() - 1; + startCoeff = 1; + } + + public SplitSegment(TrkSegment s, int pointInd, float cf) { + this.segment = s; + this.startPointInd = pointInd; + this.startCoeff = cf; + } + + public float setLastPoint(int pointInd, float endCf) { + endCoeff = endCf; + endPointInd = pointInd; + return startCoeff; + } + + } + + private abstract static class SplitMetric { + + public abstract int metric(WptPt p1, WptPt p2); + + } + public static class GPXFile extends GPXExtensions { public String author; public List tracks = new ArrayList(); @@ -124,6 +353,98 @@ public class GPXUtilities { return "cloudmade".equalsIgnoreCase(author); } + public GPXTrackAnalysis getAnalysis(long fileTimestamp) { + GPXTrackAnalysis g = new GPXTrackAnalysis(); + g.wptPoints = points.size(); + List splitSegments = new ArrayList(); + for(int i = 0; i< tracks.size() ; i++){ + Track subtrack = tracks.get(i); + for(TrkSegment segment : subtrack.segments){ + g.totalTracks ++; + if(segment.points.size() > 1) { + splitSegments.add(new SplitSegment(segment)); + } + } + } + g.prepareInformation(fileTimestamp, splitSegments.toArray(new SplitSegment[splitSegments.size()])); + return g ; + } + + + public List splitByDistance(int meters) { + + return split(new SplitMetric() { + + private float[] calculations = new float[1]; + + @Override + public int metric(WptPt p1, WptPt p2) { + net.osmand.Location.distanceBetween(p1.lat, p1.lon, p2.lat, p2.lon, calculations); + return (int) calculations[0]; + } + }, meters); + } + + public List splitByTime(int seconds) { + + return split(new SplitMetric() { + + private float[] calculations = new float[1]; + + @Override + public int metric(WptPt p1, WptPt p2) { + if(p1.time != 0 && p2.time != 0) { + return (int) ((p1.time - p2.time) / 1000); + } + return 0; + } + }, seconds); + } + + public List split(SplitMetric m, int metricLimit) { + int ml = metricLimit; + List splitSegments = new ArrayList(); + for(int i = 0; i< tracks.size() ; i++){ + Track subtrack = tracks.get(i); + for(int j = 0; j 0) { + WptPt prev = segment.points.get(k - 1); + int currentSegment = m.metric(prev, point); + while(total + currentSegment > ml) { + int p = ml - total; + float cf = sp.setLastPoint(k - 1, p / ((float)currentSegment)); + sp = new SplitSegment(segment, k - 1, cf); + sp.metricEnd = ml; + ml += metricLimit; + } + total += currentSegment; + } + } + if(segment.points.size() > 0 && !(sp.endPointInd == segment.points.size() -1 && sp.startCoeff == 1)) { + sp.metricEnd = total; + sp.setLastPoint(0, 1); + splitSegments.add(sp); + } + } + } + return convert(splitSegments); + } + + private List convert(List splitSegments) { + List ls = new ArrayList(); + for(SplitSegment s : splitSegments) { + GPXTrackAnalysis a = new GPXTrackAnalysis(); + a.prepareInformation(0, s); + ls.add(a); + } + return ls; + } + public boolean hasRtePt() { for(Route r : routes) { if(r.points.size() > 0) { @@ -621,4 +942,4 @@ public class GPXUtilities { } -} \ No newline at end of file +} diff --git a/OsmAnd/src/net/osmand/plus/activities/FavouritesActivity.java b/OsmAnd/src/net/osmand/plus/activities/FavouritesActivity.java index a01357d70a..afde5bd8ee 100644 --- a/OsmAnd/src/net/osmand/plus/activities/FavouritesActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/FavouritesActivity.java @@ -25,6 +25,8 @@ import net.osmand.plus.FavouritesDbHelper; import net.osmand.plus.GPXUtilities; import net.osmand.plus.GPXUtilities.GPXFile; import net.osmand.plus.GPXUtilities.WptPt; +import net.osmand.plus.sherpafy.TourSelectionFragment; +import net.osmand.plus.sherpafy.TourCommonActivity.TabsAdapter; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; @@ -36,6 +38,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; +import android.support.v4.view.ViewPager; import android.text.Spannable; import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; @@ -48,6 +51,7 @@ import android.widget.CheckBox; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ImageView; +import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; @@ -77,6 +81,7 @@ public class FavouritesActivity extends OsmandExpandableListActivity { private Set groupsToDelete = new LinkedHashSet(); private Comparator favoritesComparator; private ActionMode actionMode; + private TabsAdapter mTabsAdapter; @Override @@ -101,6 +106,20 @@ public class FavouritesActivity extends OsmandExpandableListActivity { setContentView(R.layout.favourites_list); getSupportActionBar().setTitle(R.string.favourites_activity); setSupportProgressBarIndeterminateVisibility(false); + TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost); + tabHost.setup(); + + ViewPager mViewPager = (ViewPager)findViewById(R.id.pager); + mTabsAdapter = new TabsAdapter(this, tabHost, mViewPager); + mTabsAdapter.addTab(tabHost.newTabSpec(TOUR_INFO).setIndicator(getString(R.string.tab_current_tour)), + TourInformationFragment.class, null); + mTabsAdapter.addTab(tabHost.newTabSpec(TOUR_STAGE).setIndicator(getString(R.string.tab_stages)), + TourStageFragment.class, null); + mTabsAdapter.addTab(tabHost.newTabSpec(TOUR_SELECTION).setIndicator(getString(R.string.tab_tours)), + TourSelectionFragment.class, null); + if (savedInstanceState != null) { + tabHost.setCurrentTabByTag(savedInstanceState.getString("tab")); + } // getSupportActionBar().setIcon(R.drawable.tab_search_favorites_icon); diff --git a/OsmAnd/src/net/osmand/plus/activities/FavouritesTreeFragment.java b/OsmAnd/src/net/osmand/plus/activities/FavouritesTreeFragment.java new file mode 100644 index 0000000000..df26af2c43 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/FavouritesTreeFragment.java @@ -0,0 +1,701 @@ +package net.osmand.plus.activities; + +import java.io.File; +import java.text.Collator; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.londatiga.android.ActionItem; +import net.londatiga.android.QuickAction; +import net.osmand.access.AccessibleToast; +import net.osmand.data.FavouritePoint; +import net.osmand.data.LatLon; +import net.osmand.plus.FavouritesDbHelper; +import net.osmand.plus.GPXUtilities; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandSettings; +import net.osmand.plus.R; +import net.osmand.plus.GPXUtilities.GPXFile; +import net.osmand.plus.GPXUtilities.WptPt; +import net.osmand.plus.activities.FavouritesActivity.FavouritesAdapter; +import net.osmand.plus.sherpafy.TourSelectionFragment; +import net.osmand.plus.sherpafy.TourCommonActivity.TabsAdapter; +import net.osmand.util.MapUtils; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.ListFragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.view.ViewPager; +import android.text.Spannable; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ExpandableListView; +import android.widget.ImageView; +import android.widget.TabHost; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; +import com.actionbarsherlock.view.ActionMode.Callback; + +public class FavouritesTreeFragment extends Fragment{ + + public static final int EXPORT_ID = 0; + public static final int IMPORT_ID = 1; + public static final int DELETE_ID = 2; + public static final int DELETE_ACTION_ID = 3; + public static final int SHARE_ID = 4; + + + private FavouritesAdapter favouritesAdapter; + private FavouritesDbHelper helper; + + private boolean selectionMode = false; + private Set favoritesToDelete = new LinkedHashSet(); + private Set groupsToDelete = new LinkedHashSet(); + private Comparator favoritesComparator; + private ActionMode actionMode; + private TabsAdapter mTabsAdapter; + + + @Override + public void onCreate(Bundle icicle) { + //This has to be called before setContentView and you must use the + //class in com.actionbarsherlock.view and NOT android.view + final Collator collator = Collator.getInstance(); + collator.setStrength(Collator.SECONDARY); + favoritesComparator = new Comparator(){ + + @Override + public int compare(FavouritePoint object1, FavouritePoint object2) { + return collator.compare(object1.getName(), object2.getName()); + } + }; + + + + setContentView(R.layout.favourites_list); + helper = getMyApplication().getFavorites(); + favouritesAdapter = new FavouritesAdapter(); + favouritesAdapter.setFavoriteGroups(helper.getFavoriteGroups()); + getExpandableListView().setAdapter(favouritesAdapter); + + } + + private void deleteFavorites() { + new AsyncTask(){ + + @Override + protected void onPreExecute() { + showProgressBar(); + }; + @Override + protected void onPostExecute(String result) { + hideProgressBar(); + favouritesAdapter.synchronizeGroups(); + favouritesAdapter.sort(favoritesComparator); + } + + @Override + protected void onProgressUpdate(Object... values) { + for(Object o : values){ + if(o instanceof FavouritePoint){ + favouritesAdapter.deleteFavoritePoint((FavouritePoint) o); + } else if(o instanceof String){ + favouritesAdapter.deleteCategory((String) o); + } + } + } + + @Override + protected String doInBackground(Void... params) { + for (FavouritePoint fp : favoritesToDelete) { + helper.deleteFavourite(fp); + publishProgress(fp); + } + favoritesToDelete.clear(); + for (String group : groupsToDelete) { + helper.deleteGroup(group); + publishProgress(group); + } + groupsToDelete.clear(); + return getString(R.string.favourites_delete_multiple_succesful); + } + + }.execute(); + + } + + @Override + protected void onResume() { + super.onResume(); +// final LatLon mapLocation = getMyApplication().getSettings().getLastKnownMapLocation(); + favouritesAdapter.synchronizeGroups(); + +// Sort Favs by distance on Search tab, but sort alphabetically here + favouritesAdapter.sort(favoritesComparator); + + } + + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { + if(selectionMode){ + CheckBox ch = (CheckBox) v.findViewById(R.id.check_item); + FavouritePoint model = favouritesAdapter.getChild(groupPosition, childPosition); + ch.setChecked(!ch.isChecked()); + if(ch.isChecked()){ + favoritesToDelete.add(model); + } else { + favoritesToDelete.remove(model); + } + } else { + final QuickAction qa = new QuickAction(v); + final OsmandSettings settings = getMyApplication().getSettings(); + final FavouritePoint point = (FavouritePoint) favouritesAdapter.getChild(groupPosition, childPosition); + String name = getString(R.string.favorite) + ": " + point.getName(); + LatLon location = new LatLon(point.getLatitude(), point.getLongitude()); + OnClickListener onshow = new OnClickListener() { + @Override + public void onClick(View v) { + settings.SHOW_FAVORITES.set(true); + } + }; + MapActivityActions.createDirectionsActions(qa, location, point, name, settings.getLastKnownMapZoom(), this, + true, onshow, false); + if (point.isStored()) { + ActionItem edit = new ActionItem(); + edit.setIcon(getResources().getDrawable(R.drawable.ic_action_edit_light)); + edit.setTitle(getString(R.string.favourites_context_menu_edit)); + edit.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + editPoint(point); + qa.dismiss(); + } + }); + qa.addActionItem(edit); + + ActionItem delete = new ActionItem(); + delete.setTitle(getString(R.string.favourites_context_menu_delete)); + delete.setIcon(getResources().getDrawable(R.drawable.ic_action_delete_light)); + delete.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + deletePoint(point); + qa.dismiss(); + } + }); + qa.addActionItem(delete); + } + + qa.show(); + } + return true; + } + + + private boolean editPoint(final FavouritePoint point) { + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.favourites_context_menu_edit); + final View v = getLayoutInflater().inflate(R.layout.favourite_edit_dialog, getExpandableListView(), false); + final AutoCompleteTextView cat = (AutoCompleteTextView) v.findViewById(R.id.Category); + final EditText editText = (EditText) v.findViewById(R.id.Name); + builder.setView(v); + editText.setText(point.getName()); + cat.setText(point.getCategory()); + cat.setThreshold(1); + cat.setAdapter(new ArrayAdapter(this, R.layout.list_textview, helper.getFavoriteGroups().keySet().toArray(new String[] {}))); + builder.setNegativeButton(R.string.default_buttons_cancel, null); + builder.setPositiveButton(R.string.default_buttons_apply, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + boolean editied = helper.editFavouriteName(point, editText.getText().toString().trim(), cat.getText().toString()); + if (editied) { + favouritesAdapter.synchronizeGroups(); + favouritesAdapter.sort(favoritesComparator); + } + + } + }); + builder.create().show(); + editText.requestFocus(); + return true; + } + + private boolean deletePoint(final FavouritePoint point) { + final Resources resources = this.getResources(); + Builder builder = new AlertDialog.Builder(this); + builder.setMessage(getString(R.string.favourites_remove_dialog_msg, point.getName())); + builder.setNegativeButton(R.string.default_buttons_no, null); + builder.setPositiveButton(R.string.default_buttons_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + boolean deleted = helper.deleteFavourite(point); + if (deleted) { + AccessibleToast.makeText(FavouritesActivity.this, + MessageFormat.format(resources.getString(R.string.favourites_remove_dialog_success), point.getName()), + Toast.LENGTH_SHORT).show(); + favouritesAdapter.synchronizeGroups(); + favouritesAdapter.sort(favoritesComparator); + } + + } + }); + builder.create().show(); + return true; + } + + @Override + public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) { + if (item.getItemId() == EXPORT_ID) { + export(); + return true; + } else if (item.getItemId() == IMPORT_ID) { + importFile(); + return true; + } else if (item.getItemId() == SHARE_ID) { + shareFavourites(); + return true; + } else if (item.getItemId() == DELETE_ID) { + enterDeleteMode(); + return true; + } else if (item.getItemId() == DELETE_ACTION_ID) { + deleteFavoritesAction(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + + @Override + public boolean onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu) { + createMenuItem(menu, EXPORT_ID, R.string.export_fav, R.drawable.ic_action_gsave_light, R.drawable.ic_action_gsave_dark, + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + createMenuItem(menu, SHARE_ID, R.string.share_fav, R.drawable.ic_action_gshare_light, R.drawable.ic_action_gshare_dark, + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + createMenuItem(menu, IMPORT_ID, R.string.import_fav, R.drawable.ic_action_grefresh_light, R.drawable.ic_action_grefresh_dark, + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + createMenuItem(menu, DELETE_ID, R.string.default_buttons_delete, R.drawable.ic_action_delete_light, R.drawable.ic_action_delete_dark, + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT) ; + return super.onCreateOptionsMenu(menu); + } + + public void showProgressBar(){ + setSupportProgressBarIndeterminateVisibility(true); + } + + public void hideProgressBar(){ + setSupportProgressBarIndeterminateVisibility(false); + } + + + private void enterDeleteMode() { + actionMode = startActionMode(new Callback() { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + selectionMode = true; + createMenuItem(menu, DELETE_ACTION_ID, R.string.default_buttons_delete, R.drawable.ic_action_delete_light, R.drawable.ic_action_delete_dark, + MenuItem.SHOW_AS_ACTION_IF_ROOM); + favoritesToDelete.clear(); + groupsToDelete.clear(); + favouritesAdapter.notifyDataSetInvalidated(); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + selectionMode = false; + favouritesAdapter.notifyDataSetInvalidated(); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if(item.getItemId() == DELETE_ACTION_ID) { + deleteFavoritesAction(); + } + return true; + } + + + }); + + } + + private void deleteFavoritesAction() { + if (groupsToDelete.size() + favoritesToDelete.size() > 0) { + + Builder b = new AlertDialog.Builder(FavouritesActivity.this); + b.setMessage(getString(R.string.favorite_delete_multiple, favoritesToDelete.size(), groupsToDelete.size())); + b.setPositiveButton(R.string.default_buttons_delete, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if(actionMode != null) { + actionMode.finish(); + } + deleteFavorites(); + } + }); + b.setNegativeButton(R.string.default_buttons_cancel, null); + b.show(); + } + } + + private void importFile() { + final File tosave = getMyApplication().getAppPath(FavouritesDbHelper.FILE_TO_SAVE); + if (!tosave.exists()) { + AccessibleToast.makeText(this, MessageFormat.format(getString(R.string.fav_file_to_load_not_found), tosave.getAbsolutePath()), + Toast.LENGTH_LONG).show(); + } else { + new AsyncTask() { + @Override + protected String doInBackground(Void... params) { + Set existedPoints = new LinkedHashSet(); + if (!favouritesAdapter.isEmpty()) { + for (FavouritePoint fp : helper.getFavouritePoints()) { + existedPoints.add(fp.getName() + "_" + fp.getCategory()); + } + } + GPXFile res = GPXUtilities.loadGPXFile(getMyApplication(), tosave); + if (res.warning != null) { + return res.warning; + } + for (WptPt p : res.points) { + if (existedPoints.contains(p.name) || existedPoints.contains(p.name + "_" + p.category)) { + continue; + } + int c; + String name = p.name; + String categoryName = p.category != null ? p.category : ""; + if (name == null) { + name = ""; + } + // old way to store the category, in name. + if ("".equals(categoryName.trim()) && (c = p.name.lastIndexOf('_')) != -1) { + categoryName = p.name.substring(c + 1); + name = p.name.substring(0, c); + } + FavouritePoint fp = new FavouritePoint(p.lat, p.lon, name, categoryName); + if (helper.addFavourite(fp)) { + publishProgress(fp); + } + } + return null; + } + + @Override + protected void onProgressUpdate(FavouritePoint... values) { + for (FavouritePoint p : values) { + favouritesAdapter.addFavoritePoint(p); + } + }; + + @Override + protected void onPreExecute() { + showProgressBar(); + }; + + @Override + protected void onPostExecute(String warning) { + hideProgressBar(); + if (warning == null) { + AccessibleToast.makeText(FavouritesActivity.this, R.string.fav_imported_sucessfully, Toast.LENGTH_SHORT).show(); + } else { + AccessibleToast.makeText(FavouritesActivity.this, warning, Toast.LENGTH_LONG).show(); + } + favouritesAdapter.synchronizeGroups(); + favouritesAdapter.sort(favoritesComparator); + }; + + }.execute(); + } + } + + private void shareFavourites() { + if (favouritesAdapter.isEmpty()) { + AccessibleToast.makeText(this, R.string.no_fav_to_save, Toast.LENGTH_LONG).show(); + } else { + final AsyncTask exportTask = new AsyncTask() { + @Override + protected GPXFile doInBackground(Void... params) { + return helper.asGpxFile(); + } + + @Override + protected void onPreExecute() { + showProgressBar(); + } + + @Override + protected void onPostExecute(GPXFile gpxFile) { + hideProgressBar(); + final Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, GPXUtilities.asString(gpxFile, getMyApplication())); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_fav_subject)); + sendIntent.setType("application/gpx+xml"); + startActivity(sendIntent); + } + }; + + exportTask.execute(); + } + } + + private void export() { + final File tosave = getMyApplication().getAppPath(FavouritesDbHelper.FILE_TO_SAVE); + if (favouritesAdapter.isEmpty()) { + AccessibleToast.makeText(this, R.string.no_fav_to_save, Toast.LENGTH_LONG).show(); + } else if (!tosave.getParentFile().exists()) { + AccessibleToast.makeText(this, R.string.sd_dir_not_accessible, Toast.LENGTH_LONG).show(); + } else { + final AsyncTask exportTask = new AsyncTask() { + @Override + protected String doInBackground(Void... params) { + return helper.exportFavorites(); + } + + @Override + protected void onPreExecute() { + showProgressBar(); + }; + + @Override + protected void onPostExecute(String warning) { + hideProgressBar(); + if (warning == null) { + AccessibleToast.makeText(FavouritesActivity.this, + MessageFormat.format(getString(R.string.fav_saved_sucessfully), tosave.getAbsolutePath()), + Toast.LENGTH_LONG).show(); + } else { + AccessibleToast.makeText(FavouritesActivity.this, warning, Toast.LENGTH_LONG).show(); + } + }; + }; + + if (tosave.exists()) { + Builder bld = new AlertDialog.Builder(this); + bld.setPositiveButton(R.string.default_buttons_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + exportTask.execute(); + } + }); + bld.setNegativeButton(R.string.default_buttons_no, null); + bld.setMessage(R.string.fav_export_confirmation); + bld.show(); + } else { + exportTask.execute(); + } + } + } + + + class FavouritesAdapter extends OsmandBaseExpandableListAdapter { + + Map> sourceFavoriteGroups; + Map> favoriteGroups = new LinkedHashMap>(); + List groups = new ArrayList(); + + public void setFavoriteGroups(Map> favoriteGroups) { + this.sourceFavoriteGroups = favoriteGroups; + synchronizeGroups(); + } + + public void sort(Comparator comparator) { + for(List ps : favoriteGroups.values()) { + Collections.sort(ps, comparator); + } + } + + public void addFavoritePoint(FavouritePoint p) { + if(!favoriteGroups.containsKey(p.getCategory())){ + favoriteGroups.put(p.getCategory(), new ArrayList()); + groups.add(p.getCategory()); + } + favoriteGroups.get(p.getCategory()).add(p); + notifyDataSetChanged(); + } + + public void deleteFavoritePoint(FavouritePoint p) { + if(favoriteGroups.containsKey(p.getCategory())){ + favoriteGroups.get(p.getCategory()).remove(p); + } + notifyDataSetChanged(); + } + + public void deleteCategory(String p) { + favoriteGroups.remove(p); + groups.remove(p); + notifyDataSetChanged(); + } + + public void synchronizeGroups(){ + favoriteGroups.clear(); + groups.clear(); + for(String key : sourceFavoriteGroups.keySet()){ + groups.add(key); + favoriteGroups.put(key, new ArrayList(sourceFavoriteGroups.get(key))); + } + notifyDataSetChanged(); + } + + @Override + public FavouritePoint getChild(int groupPosition, int childPosition) { + return favoriteGroups.get(groups.get(groupPosition)).get(childPosition); + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return groupPosition * 10000 + childPosition; + } + + @Override + public int getChildrenCount(int groupPosition) { + return favoriteGroups.get(groups.get(groupPosition)).size(); + } + + @Override + public String getGroup(int groupPosition) { + return groups.get(groupPosition); + } + + @Override + public int getGroupCount() { + return groups.size(); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + View row = convertView; + if (row == null) { + LayoutInflater inflater = getLayoutInflater(); + row = inflater.inflate(R.layout.expandable_list_item_category, parent, false); + fixBackgroundRepeat(row); + } + adjustIndicator(groupPosition, isExpanded, row); + TextView label = (TextView) row.findViewById(R.id.category_name); + final String model = getGroup(groupPosition); + label.setText(model); + final CheckBox ch = (CheckBox) row.findViewById(R.id.check_item); + + if(selectionMode){ + ch.setVisibility(View.VISIBLE); + ch.setChecked(groupsToDelete.contains(model)); + + ch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (ch.isChecked()) { + groupsToDelete.add(model); + List fvs = helper.getFavoriteGroups().get(model); + if (fvs != null) { + favoritesToDelete.addAll(fvs); + } + favouritesAdapter.notifyDataSetInvalidated(); + } else { + groupsToDelete.remove(model); + } + } + }); + } else { + ch.setVisibility(View.GONE); + } + return row; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + View row = convertView; + if (row == null) { + LayoutInflater inflater = getLayoutInflater(); + row = inflater.inflate(R.layout.favourites_list_item, parent, false); + } + + TextView label = (TextView) row.findViewById(R.id.favourite_label); + ImageView icon = (ImageView) row.findViewById(R.id.favourite_icon); + final FavouritePoint model = (FavouritePoint) getChild(groupPosition, childPosition); + row.setTag(model); + if(model.isStored()){ + icon.setImageResource(R.drawable.list_favorite); + } else { + icon.setImageResource(R.drawable.opened_poi); + } + LatLon lastKnownMapLocation = getMyApplication().getSettings().getLastKnownMapLocation(); + int dist = (int) (MapUtils.getDistance(model.getLatitude(), model.getLongitude(), + lastKnownMapLocation.getLatitude(), lastKnownMapLocation.getLongitude())); + String distance = OsmAndFormatter.getFormattedDistance(dist, getMyApplication()) + " "; + label.setText(distance + model.getName(), TextView.BufferType.SPANNABLE); + ((Spannable) label.getText()).setSpan(new ForegroundColorSpan(getResources().getColor(R.color.color_distance)), 0, distance.length() - 1, 0); + final CheckBox ch = (CheckBox) row.findViewById(R.id.check_item); + if(selectionMode && model.isStored()){ + ch.setVisibility(View.VISIBLE); + ch.setChecked(favoritesToDelete.contains(model)); + row.findViewById(R.id.favourite_icon).setVisibility(View.GONE); + ch.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + if(ch.isChecked()){ + favoritesToDelete.add(model); + } else { + favoritesToDelete.remove(model); + if(groupsToDelete.contains(model.getCategory())){ + groupsToDelete.remove(model.getCategory()); + favouritesAdapter.notifyDataSetInvalidated(); + } + } + } + }); + } else { + row.findViewById(R.id.favourite_icon).setVisibility(View.VISIBLE); + ch.setVisibility(View.GONE); + } + return row; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/activities/OsmandExpandableListFragment.java b/OsmAnd/src/net/osmand/plus/activities/OsmandExpandableListFragment.java new file mode 100644 index 0000000000..3eeb1ca247 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/activities/OsmandExpandableListFragment.java @@ -0,0 +1,52 @@ +package net.osmand.plus.activities; + +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import android.app.ActionBar; +import android.graphics.Shader.TileMode; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import android.widget.ExpandableListView; + +import com.actionbarsherlock.app.SherlockExpandableListActivity; +import com.actionbarsherlock.app.SherlockFragment; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.MenuItem.OnMenuItemClickListener; + +public abstract class OsmandExpandableListFragment extends SherlockFragment { + + + public OsmandApplication getMyApplication() { + return (OsmandApplication)getActivity().getApplication(); + } + + public + public View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup container, Bundle savedInstanceState) { + com.android.internal.R.layout.list_content + listView = new ExpandableListView(getActivity()); + return listView; + } + + public MenuItem createMenuItem(Menu m, int id, int titleRes, int iconLight, int iconDark, int menuItemType) { + int r = isLightActionBar() ? iconLight : iconDark; + MenuItem menuItem = m.add(0, id, 0, titleRes); + if (r != 0) { + menuItem.setIcon(r); + } + menuItem.setShowAsActionFlags(menuItemType).setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(com.actionbarsherlock.view.MenuItem item) { + return onOptionsItemSelected(item); + } + }); + return menuItem; + } + + + public boolean isLightActionBar() { + return ((OsmandApplication) getApplication()).getSettings().isLightActionBar(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java index 07fbf870c4..4107a84c9b 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/GpxUiHelper.java @@ -12,9 +12,7 @@ import net.osmand.access.AccessibleToast; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.GPXUtilities; import net.osmand.plus.GPXUtilities.GPXFile; -import net.osmand.plus.GPXUtilities.Track; -import net.osmand.plus.GPXUtilities.TrkSegment; -import net.osmand.plus.GPXUtilities.WptPt; +import net.osmand.plus.GPXUtilities.GPXTrackAnalysis; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -42,130 +40,46 @@ import android.widget.Toast; public class GpxUiHelper { public static String getDescription(OsmandApplication app, GPXFile result, File f) { + GPXTrackAnalysis analysis = result.getAnalysis(f.lastModified()); + return getDescription(app, analysis); + } + + public static String getDescription(OsmandApplication app, GPXTrackAnalysis analysis) { StringBuilder description = new StringBuilder(); - float totalDistance = 0; - int totalTracks = 0; - long startTime = Long.MAX_VALUE; - long endTime = Long.MIN_VALUE; - long timeSpan = 0; - long timeMoving = 0; - float totalDistanceMoving = 0; - - double diffElevationUp = 0; - double diffElevationDown = 0; - double totalElevation = 0; - double minElevation = 99999; - double maxElevation = 0; - - float maxSpeed = 0; - int speedCount = 0; - double totalSpeedSum = 0; - - float[] calculations = new float[1]; - - int points = 0; - for(int i = 0; i< result.tracks.size() ; i++){ - Track subtrack = result.tracks.get(i); - for(TrkSegment segment : subtrack.segments){ - totalTracks++; - points += segment.points.size(); - for (int j = 0; j < segment.points.size(); j++) { - WptPt point = segment.points.get(j); - long time = point.time; - if(time != 0){ - startTime = Math.min(startTime, time); - endTime = Math.max(startTime, time); - } - - double elevation = point.ele; - if (!Double.isNaN(elevation)) { - totalElevation += elevation; - minElevation = Math.min(elevation, minElevation); - maxElevation = Math.max(elevation, maxElevation); - } - - float speed = (float) point.speed; - if(speed > 0){ - totalSpeedSum += speed; - maxSpeed = Math.max(speed, maxSpeed); - speedCount ++; - } - - if (j > 0) { - WptPt prev = segment.points.get(j - 1); - - if (!Double.isNaN(point.ele) && !Double.isNaN(prev.ele)) { - double diff = point.ele - prev.ele; - if (diff > 0) { - diffElevationUp += diff; - } else { - diffElevationDown -= diff; - } - } - - //totalDistance += MapUtils.getDistance(prev.lat, prev.lon, point.lat, point.lon); - // using ellipsoidal 'distanceBetween' instead of spherical haversine (MapUtils.getDistance) is a little more exact, also seems slightly faster: - net.osmand.Location.distanceBetween(prev.lat, prev.lon, point.lat, point.lon, calculations); - totalDistance += calculations[0]; - - // Averaging speed values is less exact than totalDistance/timeMoving - if(speed > 0 && point.time != 0 && prev.time != 0){ - timeMoving = timeMoving + (point.time - prev.time); - totalDistanceMoving += calculations[0]; - } - } - } - } - } - if(startTime == Long.MAX_VALUE){ - startTime = f.lastModified(); - } - if(endTime == Long.MIN_VALUE){ - endTime = f.lastModified(); - } - // OUTPUT: // 1. Total distance, Start time, End time - description.append(app.getString(R.string.local_index_gpx_info, totalTracks, points, - result.points.size(), OsmAndFormatter.getFormattedDistance(totalDistance, app), - startTime, endTime)); + description.append(app.getString(R.string.local_index_gpx_info, analysis.totalTracks, analysis.points, + analysis.wptPoints, OsmAndFormatter.getFormattedDistance(analysis.totalDistance, app), + analysis.startTime, analysis.endTime)); // 2. Time span - timeSpan = endTime - startTime; - description.append(app.getString(R.string.local_index_gpx_timespan, (int) ((timeSpan / 1000) / 3600), (int) (((timeSpan / 1000) / 60) % 60), (int) ((timeSpan / 1000) % 60))); + description.append(app.getString(R.string.local_index_gpx_timespan, analysis.getTimeHours(analysis.timeSpan), + analysis.getTimeMinutes(analysis.timeSpan), analysis.getTimeSeconds(analysis.timeSpan))); // 3. Time moving, if any - if(timeMoving > 0){ + if(analysis.isTimeMoving()){ description.append( - app.getString(R.string.local_index_gpx_timemoving, (int) ((timeMoving / 1000) / 3600), (int) (((timeMoving / 1000) / 60) % 60), (int) ((timeMoving / 1000) % 60))); + app.getString(R.string.local_index_gpx_timemoving, analysis.getTimeHours(analysis.timeMoving), + analysis.getTimeMinutes(analysis.timeMoving), analysis.getTimeSeconds(analysis.timeMoving))); } // 4. Elevation, eleUp, eleDown, if recorded - if(totalElevation != 0 || diffElevationUp != 0 || diffElevationDown != 0){ + if(analysis.isElevationSpecified()){ description.append( app.getString(R.string.local_index_gpx_info_elevation, - OsmAndFormatter.getFormattedAlt(totalElevation / points, app), - OsmAndFormatter.getFormattedAlt(minElevation, app), - OsmAndFormatter.getFormattedAlt(maxElevation, app), - OsmAndFormatter.getFormattedAlt(diffElevationUp, app), - OsmAndFormatter.getFormattedAlt(diffElevationDown, app))); + OsmAndFormatter.getFormattedAlt(analysis.avgElevation, app), + OsmAndFormatter.getFormattedAlt(analysis.minElevation, app), + OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app), + OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app), + OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app))); } - // 5. Max speed and Average speed, if any. Average speed is NOT overall (effective) speed, but only calculated for "moving" periods. - if(speedCount > 0){ - if(timeMoving > 0){ + + if(analysis.isSpeedSpecified()){ description.append( app.getString(R.string.local_index_gpx_info_speed, - OsmAndFormatter.getFormattedSpeed((float) (totalDistanceMoving / timeMoving * 1000), app), - OsmAndFormatter.getFormattedSpeed(maxSpeed, app))); - // (Use totalDistanceMoving instead of totalDistance for av-speed to ignore effect of position fluctuations at rest) - } else { - // Averaging speed values is less exact than totalDistance/timeMoving - description.append( - app.getString(R.string.local_index_gpx_info_speed, - OsmAndFormatter.getFormattedSpeed((float) (totalSpeedSum / speedCount), app), - OsmAndFormatter.getFormattedSpeed(maxSpeed, app))); - } + OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app), + OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app))); } return description.toString(); } diff --git a/OsmAnd/src/net/osmand/plus/osmo/OsMoControlDevice.java b/OsmAnd/src/net/osmand/plus/osmo/OsMoControlDevice.java index 3554fcdff1..2a23b5a254 100644 --- a/OsmAnd/src/net/osmand/plus/osmo/OsMoControlDevice.java +++ b/OsmAnd/src/net/osmand/plus/osmo/OsMoControlDevice.java @@ -2,6 +2,7 @@ package net.osmand.plus.osmo; import java.util.List; +import net.osmand.Location; import net.osmand.data.LatLon; import net.osmand.plus.NavigationService; import net.osmand.plus.OsmandApplication; @@ -54,7 +55,16 @@ public class OsMoControlDevice implements OsMoReactor { @Override public boolean acceptCommand(String command, String id, String data, JSONObject obj, OsMoThread tread) { if(command.equals("REMOTE_CONTROL")) { - if(data.equals("BATTERY_INFO")) { + if(data.equals("PP")) { + service.pushCommand("PP"); + } else if(data.equals("FF")) { + Location ll = app.getLocationProvider().getLastKnownLocation(); + if(ll == null) { + service.pushCommand("FF|0"); + } else { + service.pushCommand("FF|"+OsMoTracker.formatLocation(ll)); + } + } else if(data.equals("BATTERY_INFO")) { try { service.pushCommand("BATTERY_INFO|"+getBatteryLevel().toString()); } catch(JSONException e) { diff --git a/OsmAnd/src/net/osmand/plus/osmo/OsMoPositionLayer.java b/OsmAnd/src/net/osmand/plus/osmo/OsMoPositionLayer.java index d7f1425dc9..08227c3025 100644 --- a/OsmAnd/src/net/osmand/plus/osmo/OsMoPositionLayer.java +++ b/OsmAnd/src/net/osmand/plus/osmo/OsMoPositionLayer.java @@ -92,7 +92,7 @@ public class OsMoPositionLayer extends OsmandMapLayer implements ContextMenuLaye pth = new Path(); pointOuter = new Paint(); - pointOuter.setColor(Color.GRAY); + pointOuter.setColor(0x88555555); pointOuter.setAntiAlias(true); pointOuter.setStyle(Style.FILL_AND_STROKE); } @@ -122,34 +122,39 @@ public class OsMoPositionLayer extends OsmandMapLayer implements ContextMenuLaye long treshold = System.currentTimeMillis() - 15000; for (OsMoDevice t : getTrackingDevices()) { Location l = t.getLastLocation(); - if (l != null) { - ConcurrentLinkedQueue plocations = t.getPreviousLocations(treshold); + ConcurrentLinkedQueue plocations = t.getPreviousLocations(treshold); + if (!plocations.isEmpty() && l != null) { int x = (int) tb.getPixXFromLatLon(l.getLatitude(), l.getLongitude()); int y = (int) tb.getPixYFromLatLon(l.getLatitude(), l.getLongitude()); - if (plocations.size() > 0) { - pth.rewind(); - Iterator it = plocations.iterator(); - boolean f= true; - while (it.hasNext()) { - Location lo = it.next(); - int xt = (int) tb.getPixXFromLatLon(lo.getLatitude(), lo.getLongitude()); - int yt = (int) tb.getPixYFromLatLon(lo.getLatitude(), lo.getLongitude()); - if(f) { - f = false; - pth.moveTo(xt, yt); - } else{ - pth.lineTo(xt, yt); - } + pth.rewind(); + Iterator it = plocations.iterator(); + boolean f = true; + while (it.hasNext()) { + Location lo = it.next(); + int xt = (int) tb.getPixXFromLatLon(lo.getLatitude(), lo.getLongitude()); + int yt = (int) tb.getPixYFromLatLon(lo.getLatitude(), lo.getLongitude()); + if (f) { + f = false; + pth.moveTo(xt, yt); + } else { + pth.lineTo(xt, yt); } - pth.lineTo(x, y); - paintPath.setColor(t.getColor()); - canvas.drawPath(pth, paintPath); } + pth.lineTo(x, y); + paintPath.setColor(t.getColor()); + canvas.drawPath(pth, paintPath); + } + } + for (OsMoDevice t : getTrackingDevices()) { + Location l = t.getLastLocation(); + if (l != null) { + int x = (int) tb.getPixXFromLatLon(l.getLatitude(), l.getLongitude()); + int y = (int) tb.getPixYFromLatLon(l.getLatitude(), l.getLongitude()); pointInnerCircle.setColor(t.getColor()); - canvas.drawCircle(x, y, r + 2, pointOuter); - canvas.drawCircle(x, y, r - 2, pointInnerCircle); - paintTextIcon.setTextSize(r); - canvas.drawText(t.getVisibleName().substring(0, 1), x, y, paintTextIcon); + canvas.drawCircle(x, y, r + (float)Math.ceil(tb.getDensity()), pointOuter); + canvas.drawCircle(x, y, r - (float)Math.ceil(tb.getDensity()), pointInnerCircle); + paintTextIcon.setTextSize(r * 3 / 2); + canvas.drawText(t.getVisibleName().substring(0, 1).toUpperCase(), x, y - r, paintTextIcon); } } } diff --git a/OsmAnd/src/net/osmand/plus/osmo/OsMoTracker.java b/OsmAnd/src/net/osmand/plus/osmo/OsMoTracker.java index cf0ebc84f1..2bfe125b94 100644 --- a/OsmAnd/src/net/osmand/plus/osmo/OsMoTracker.java +++ b/OsmAnd/src/net/osmand/plus/osmo/OsMoTracker.java @@ -78,28 +78,32 @@ public class OsMoTracker implements OsMoReactor { if(!bufferOfLocations.isEmpty()){ Location loc = bufferOfLocations.poll(); lastSendLocation = loc; - StringBuilder cmd = new StringBuilder("T|"); - cmd.append("L").append((float)loc.getLatitude()).append(":").append((float)loc.getLongitude()); - if(loc.hasAccuracy()) { - cmd.append("H").append((float)loc.getAccuracy()); - } - if(loc.hasAltitude()) { - cmd.append("A").append((float)loc.getAltitude()); - } - if(loc.hasSpeed()) { - cmd.append("S").append((float)loc.getSpeed()); - } - if(loc.hasBearing()) { - cmd.append("C").append((float)loc.getBearing()); - } - if((System.currentTimeMillis() - loc.getTime()) > 30000 && loc.getTime() != 0) { - cmd.append("T").append(loc.getTime()); - } locationsSent ++; - return cmd.toString(); + return "T|"+formatLocation(loc); } return null; } + + public static String formatLocation(Location loc) { + StringBuilder cmd = new StringBuilder(); + cmd.append("L").append((float)loc.getLatitude()).append(":").append((float)loc.getLongitude()); + if(loc.hasAccuracy()) { + cmd.append("H").append((float)loc.getAccuracy()); + } + if(loc.hasAltitude()) { + cmd.append("A").append((float)loc.getAltitude()); + } + if(loc.hasSpeed()) { + cmd.append("S").append((float)loc.getSpeed()); + } + if(loc.hasBearing()) { + cmd.append("C").append((float)loc.getBearing()); + } + if((System.currentTimeMillis() - loc.getTime()) > 30000 && loc.getTime() != 0) { + cmd.append("T").append(loc.getTime()); + } + return cmd.toString(); + } public Location getLastSendLocation() { return lastSendLocation; diff --git a/plugins/Osmand-Sherpafy/AndroidManifest.xml b/plugins/Osmand-Sherpafy/AndroidManifest.xml index eeeea53793..144a69c82d 100644 --- a/plugins/Osmand-Sherpafy/AndroidManifest.xml +++ b/plugins/Osmand-Sherpafy/AndroidManifest.xml @@ -11,7 +11,7 @@ -